artifacty 0.1.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/server.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  createArtifact,
10
10
  createStore,
11
11
  getArtifact,
12
- listArtifacts,
12
+ listArtifactsPage,
13
13
  listAuditEvents,
14
14
  MAX_ARTIFACT_BYTES,
15
15
  restoreArtifact,
@@ -177,15 +177,19 @@ export async function handleRequest({ request, response, store, host, port, secu
177
177
  query: url.searchParams.get("q") || "",
178
178
  tag: url.searchParams.get("tag") || "",
179
179
  sourceAgent: url.searchParams.get("sourceAgent") || "",
180
- includeArchived: url.searchParams.get("includeArchived") === "true"
180
+ includeArchived: url.searchParams.get("includeArchived") === "true",
181
+ limit: url.searchParams.get("limit") || undefined,
182
+ offset: url.searchParams.get("offset") || undefined
181
183
  };
182
- const artifacts = await listArtifacts(store, {
184
+ const page = await listArtifactsPage(store, {
183
185
  query: filters.query || undefined,
184
186
  tag: filters.tag || undefined,
185
187
  sourceAgent: filters.sourceAgent || undefined,
186
- includeArchived: filters.includeArchived
188
+ includeArchived: filters.includeArchived,
189
+ limit: filters.limit,
190
+ offset: filters.offset
187
191
  });
188
- return sendHtml(response, renderDashboard({ artifacts, baseUrl, filters, locale, currentPath }), 200, headOnly);
192
+ return sendHtml(response, renderDashboard({ artifacts: page.artifacts, baseUrl, filters, pagination: page, locale, currentPath }), 200, headOnly);
189
193
  }
190
194
 
191
195
  if (method === "GET" && pathname === "/new") {
@@ -240,14 +244,19 @@ export async function handleRequest({ request, response, store, host, port, secu
240
244
  }
241
245
 
242
246
  if (method === "GET" && pathname === "/api/artifacts") {
243
- const artifacts = await listArtifacts(store, {
247
+ const page = await listArtifactsPage(store, {
244
248
  query: url.searchParams.get("q") || undefined,
245
249
  tag: url.searchParams.get("tag") || undefined,
246
250
  sourceAgent: url.searchParams.get("sourceAgent") || undefined,
247
251
  includeArchived: url.searchParams.get("includeArchived") === "true",
248
- limit: url.searchParams.get("limit") || undefined
252
+ limit: url.searchParams.get("limit") || undefined,
253
+ offset: url.searchParams.get("offset") || undefined
249
254
  });
250
- return sendJson(response, { artifacts }, 200, headOnly);
255
+ return sendJson(response, {
256
+ artifacts: page.artifacts,
257
+ pagination: paginationJson(page),
258
+ search: page.search
259
+ }, 200, headOnly);
251
260
  }
252
261
 
253
262
  if (method === "GET" && pathname === "/api/audit") {
@@ -410,12 +419,13 @@ export async function handleRequest({ request, response, store, host, port, secu
410
419
  });
411
420
 
412
421
  if (pathname.endsWith("/raw")) {
422
+ const raw = rawArtifactResponse(artifact);
413
423
  response.writeHead(200, {
414
- "content-type": artifact.version.contentType,
424
+ "content-type": raw.contentType,
415
425
  "cache-control": "no-store",
416
426
  "x-content-type-options": "nosniff"
417
427
  });
418
- response.end(headOnly ? undefined : artifact.content);
428
+ response.end(headOnly ? undefined : raw.body);
419
429
  return;
420
430
  }
421
431
 
@@ -441,6 +451,17 @@ export function decorateArtifactUrls(artifact, baseUrl) {
441
451
  };
442
452
  }
443
453
 
454
+ function paginationJson(page) {
455
+ return {
456
+ total: page.total,
457
+ limit: page.limit,
458
+ offset: page.offset,
459
+ hasMore: page.hasMore,
460
+ nextOffset: page.nextOffset,
461
+ previousOffset: page.previousOffset
462
+ };
463
+ }
464
+
444
465
  export async function readJsonBody(request) {
445
466
  const raw = await readBody(request, MAX_ARTIFACT_BYTES + 1024);
446
467
  if (!raw.trim()) {
@@ -514,6 +535,44 @@ export function sendHtml(response, html, statusCode = 200, headOnly = false, con
514
535
  response.end(headOnly ? undefined : html);
515
536
  }
516
537
 
538
+ function rawArtifactResponse(artifact) {
539
+ if (artifact.version.format === "image" || artifact.version.format === "video") {
540
+ const decoded = decodeMediaContent(artifact.content);
541
+ if (decoded) {
542
+ return {
543
+ body: decoded,
544
+ contentType: mediaContentType(artifact)
545
+ };
546
+ }
547
+ }
548
+ return {
549
+ body: artifact.content,
550
+ contentType: artifact.version.contentType
551
+ };
552
+ }
553
+
554
+ function mediaContentType(artifact) {
555
+ const metadataType = String(artifact.version.metadata?.mimeType || "").toLowerCase();
556
+ if (metadataType.startsWith("image/") || metadataType.startsWith("video/")) {
557
+ return metadataType;
558
+ }
559
+ const versionType = String(artifact.version.contentType || "").toLowerCase().split(";")[0];
560
+ if (versionType.startsWith("image/") || versionType.startsWith("video/")) {
561
+ return versionType;
562
+ }
563
+ return artifact.version.format === "video" ? "video/mp4" : "image/png";
564
+ }
565
+
566
+ function decodeMediaContent(content) {
567
+ const value = String(content || "").trim();
568
+ const dataUrl = /^data:[^;,]+;base64,([A-Za-z0-9+/=_-\s]+)$/i.exec(value);
569
+ const base64 = (dataUrl ? dataUrl[1] : value).replace(/\s+/g, "").replaceAll("-", "+").replaceAll("_", "/");
570
+ if (!base64 || !/^[A-Za-z0-9+/]+={0,2}$/.test(base64)) {
571
+ return null;
572
+ }
573
+ return Buffer.from(base64, "base64");
574
+ }
575
+
517
576
  function defaultContentSecurityPolicy() {
518
577
  return [
519
578
  "default-src 'self' data: blob:",