Skip to content

Commit 05762bd

Browse files
valid zip
1 parent 5d21c4c commit 05762bd

7 files changed

Lines changed: 559 additions & 18 deletions

backend/email-previews/unhandled_crash_in_mobile_app__nullpointerexception__attempt_to_invoke_virtual_method__150_users_.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<body style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background-color: #f1f5f9; margin: 0; padding: 40px 0; color: #000000; line-height: 1.5; -webkit-font-smoothing: antialiased;">
1010
<!-- Preheader -->
1111
<div style="display:none;font-size:1px;color:#f1f5f9;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;">
12-
NullPointerException: Attempt to invoke virtual method in Mobile App affected 150 users; last seen Jun 10, 2026, 12:03 PM UTC
12+
NullPointerException: Attempt to invoke virtual method in Mobile App affected 150 users; last seen Jun 10, 2026, 01:48 PM UTC
1313
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
1414
</div>
1515

@@ -43,7 +43,7 @@ <h1 style="font-size: 32px; font-weight: 900; line-height: 1.1; margin: 0 0 16px
4343

4444

4545
<div style="font-size: 16px; font-weight: 500; color: #525252; margin: 0 0 24px; font-family: monospace;">
46-
Jun 10, 2026, 12:03 PM UTC
46+
Jun 10, 2026, 01:48 PM UTC
4747
</div>
4848

4949

@@ -84,7 +84,7 @@ <h1 style="font-size: 32px; font-weight: 900; line-height: 1.1; margin: 0 0 16px
8484
<div style="font-weight: 800; font-size: 19px; margin-bottom: 8px;">NullPointerException: Attempt to invoke virtual method</div>
8585
<div style="font-family: monospace; font-size: 13px; line-height: 1.45;">com.example.app.MainActivity.onCreate(MainActivity.java:42)</div>
8686
<div style="margin-top: 16px; padding-top: 16px; border-top: 1px solid #000; font-size: 13px; line-height: 1.55;">
87-
Rejourney grouped this crash in <strong>Mobile App</strong>. It has affected <strong>150 users</strong>. Last seen <strong>Jun 10, 2026, 12:03 PM UTC</strong>.
87+
Rejourney grouped this crash in <strong>Mobile App</strong>. It has affected <strong>150 users</strong>. Last seen <strong>Jun 10, 2026, 01:48 PM UTC</strong>.
8888
</div>
8989
</div>
9090
</div>
@@ -101,7 +101,7 @@ <h1 style="font-size: 32px; font-weight: 900; line-height: 1.1; margin: 0 0 16px
101101

102102
<tr>
103103
<td style="border-bottom: 1px solid #e5e7eb; padding: 8px 0; color: #525252; font-weight: 700; width: 38%;">Last seen</td>
104-
<td style="border-bottom: 1px solid #e5e7eb; padding: 8px 0; color: #000; font-family: monospace; font-weight: 800; text-align: right;">Jun 10, 2026, 12:03 PM UTC</td>
104+
<td style="border-bottom: 1px solid #e5e7eb; padding: 8px 0; color: #000; font-family: monospace; font-weight: 800; text-align: right;">Jun 10, 2026, 01:48 PM UTC</td>
105105
</tr>
106106

107107
<tr>

backend/email-previews/your_verification_code__123456.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ <h1 style="font-size: 32px; font-weight: 900; line-height: 1.1; margin: 0 0 16px
4141

4242

4343
<div style="font-size: 16px; font-weight: 500; color: #525252; margin: 0 0 24px; font-family: monospace;">
44-
Jun 10, 2026, 12:03 PM UTC
44+
Jun 10, 2026, 01:48 PM UTC
4545
</div>
4646

4747

backend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"@aws-sdk/client-s3": "^3.1048.0",
5353
"@aws-sdk/s3-request-presigner": "^3.1048.0",
5454
"@clickhouse/client": "^1.18.5",
55+
"archiver": "^8.0.0",
5556
"better-auth": "^1.6.11",
5657
"bullmq": "^5.76.4",
5758
"compression": "^1.7.4",
@@ -78,6 +79,7 @@
7879
},
7980
"devDependencies": {
8081
"@eslint/js": "^9.39.2",
82+
"@types/archiver": "^8.0.0",
8183
"@types/compression": "^1.7.5",
8284
"@types/cookie-parser": "^1.4.7",
8385
"@types/cors": "^2.8.17",

backend/src/__tests__/researchLake.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,19 @@ describe('research lake anonymized payload shape', () => {
6363
expect(features?.lumaGrid.every((value) => value >= 0 && value <= 15)).toBe(true);
6464
expect(features?.edgeGrid.every((value) => value >= 0 && value <= 15)).toBe(true);
6565
});
66+
67+
it('generates a valid ZIP archive containing multiple files', async () => {
68+
const files = [
69+
{ name: 'manifest.json', buffer: Buffer.from('{"test":true}', 'utf8') },
70+
{ name: 'quality.json', buffer: Buffer.from('{"usable":true}', 'utf8') },
71+
];
72+
const zipBuffer = await __researchLakeTestInternals.createZipArchiveBuffer(files);
73+
expect(zipBuffer).toBeInstanceOf(Buffer);
74+
expect(zipBuffer.length).toBeGreaterThan(0);
75+
// Verify ZIP magic bytes (PK\x03\x04)
76+
expect(zipBuffer[0]).toBe(0x50);
77+
expect(zipBuffer[1]).toBe(0x4b);
78+
expect(zipBuffer[2]).toBe(0x03);
79+
expect(zipBuffer[3]).toBe(0x04);
80+
});
6681
});

backend/src/services/researchLake.ts

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import crypto from 'node:crypto';
22
import { createRequire } from 'node:module';
33
import { gzipSync } from 'node:zlib';
44
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
5+
import { ZipArchive } from 'archiver';
56
import { pool } from '../db/client.js';
67
import {
78
downloadFromS3ForArtifact,
@@ -176,6 +177,20 @@ function jsonlGzipBuffer(rows: unknown[]): Buffer {
176177
return gzipSync(Buffer.from(rows.map((row) => JSON.stringify(row)).join('\n') + '\n', 'utf8'));
177178
}
178179

180+
function createZipArchiveBuffer(files: { name: string; buffer: Buffer }[]): Promise<Buffer> {
181+
return new Promise((resolve, reject) => {
182+
const archive = new ZipArchive({ zlib: { level: 9 } });
183+
const buffers: Buffer[] = [];
184+
archive.on('data', (data: Buffer) => buffers.push(data));
185+
archive.on('end', () => resolve(Buffer.concat(buffers)));
186+
archive.on('error', (err: any) => reject(err));
187+
for (const file of files) {
188+
archive.append(file.buffer, { name: file.name });
189+
}
190+
archive.finalize();
191+
});
192+
}
193+
179194
async function putResearchObject(key: string, body: Buffer, contentType: string): Promise<void> {
180195
await getResearchLakeClient().send(new PutObjectCommand({
181196
Bucket: config.RESEARCH_LAKE_BUCKET,
@@ -1211,6 +1226,7 @@ async function processJob(job: ResearchJobRow): Promise<'exported' | 'rejected'>
12111226
ui_frames: `${basePath}/ui_frames.jsonl.gz`,
12121227
ui_skeleton: `${basePath}/ui_skeleton.jsonl.gz`,
12131228
quality: `${basePath}/quality.json`,
1229+
zip: `${basePath}.zip`,
12141230
},
12151231
};
12161232
const quality = {
@@ -1244,11 +1260,27 @@ async function processJob(job: ResearchJobRow): Promise<'exported' | 'rejected'>
12441260
return 'rejected';
12451261
}
12461262

1247-
await putResearchObject(`${basePath}/manifest.json`, jsonBuffer(manifest), 'application/json');
1248-
await putResearchObject(`${basePath}/quality.json`, jsonBuffer(quality), 'application/json');
1249-
await putResearchObject(`${basePath}/interactions.jsonl.gz`, jsonlGzipBuffer(interactions), 'application/jsonl+gzip');
1250-
await putResearchObject(`${basePath}/ui_frames.jsonl.gz`, jsonlGzipBuffer(visualRows.frames), 'application/jsonl+gzip');
1251-
await putResearchObject(`${basePath}/ui_skeleton.jsonl.gz`, jsonlGzipBuffer(skeleton), 'application/jsonl+gzip');
1263+
const manifestBuf = jsonBuffer(manifest);
1264+
const qualityBuf = jsonBuffer(quality);
1265+
const interactionsBuf = jsonlGzipBuffer(interactions);
1266+
const uiFramesBuf = jsonlGzipBuffer(visualRows.frames);
1267+
const uiSkeletonBuf = jsonlGzipBuffer(skeleton);
1268+
1269+
await putResearchObject(`${basePath}/manifest.json`, manifestBuf, 'application/json');
1270+
await putResearchObject(`${basePath}/quality.json`, qualityBuf, 'application/json');
1271+
await putResearchObject(`${basePath}/interactions.jsonl.gz`, interactionsBuf, 'application/jsonl+gzip');
1272+
await putResearchObject(`${basePath}/ui_frames.jsonl.gz`, uiFramesBuf, 'application/jsonl+gzip');
1273+
await putResearchObject(`${basePath}/ui_skeleton.jsonl.gz`, uiSkeletonBuf, 'application/jsonl+gzip');
1274+
1275+
const zipFiles = [
1276+
{ name: 'manifest.json', buffer: manifestBuf },
1277+
{ name: 'quality.json', buffer: qualityBuf },
1278+
{ name: 'interactions.jsonl.gz', buffer: interactionsBuf },
1279+
{ name: 'ui_frames.jsonl.gz', buffer: uiFramesBuf },
1280+
{ name: 'ui_skeleton.jsonl.gz', buffer: uiSkeletonBuf },
1281+
];
1282+
const zipBuffer = await createZipArchiveBuffer(zipFiles);
1283+
await putResearchObject(`${basePath}.zip`, zipBuffer, 'application/zip');
12521284

12531285
await completeJob(job, {
12541286
status: 'exported',
@@ -1315,4 +1347,5 @@ export async function runResearchLakeExtractionCycle(): Promise<ResearchLakeCycl
13151347
export const __researchLakeTestInternals = {
13161348
containsIdentifierRisk,
13171349
imageFeatureGrid,
1350+
createZipArchiveBuffer,
13181351
};

0 commit comments

Comments
 (0)