All files / lib/packages/shared-utils draft-attachments.ts

90.78% Statements 69/76
81.81% Branches 72/88
100% Functions 24/24
88.7% Lines 55/62

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154                      330x 20x   330x 48x           330x 8x         330x 29x 9x     178x 20x       178x 20x 2x     18x     330x 16x   330x       13x 13x 5x     168x 8x       168x 8x       8x 8x             330x 8x   330x     18x     18x 4x     14x 168x 51x   14x 22x             10x 5x     5x     14x 22x         22x           21x 22x       22x 22x       22x   22x   22x 22x   22x 22x       22x                        
import type { opensearch } from "shared-types";
import { events } from "shared-types/events";
 
type ShapeLike = {
  shape: Record<string, unknown>;
};
 
type SafeParseLike = {
  safeParse: (value: unknown) => { success: boolean; data?: unknown };
};
 
const hasBaseSchema = (eventModule: unknown): eventModule is { baseSchema: unknown } =>
  typeof eventModule === "object" && eventModule !== null && "baseSchema" in eventModule;
 
const hasShape = (schema: unknown): schema is ShapeLike =>
  typeof schema === "object" &&
  schema !== null &&
  "shape" in schema &&
  typeof (schema as { shape?: unknown }).shape === "object" &&
  (schema as { shape?: unknown }).shape !== null;
 
const hasSafeParse = (schema: unknown): schema is SafeParseLike =>
  typeof schema === "object" &&
  schema !== null &&
  "safeParse" in schema &&
  typeof (schema as { safeParse?: unknown }).safeParse === "function";
 
const getDraftAttachmentShape = (eventName?: opensearch.main.Document["event"]) => {
  if (!eventName || !(eventName in events)) {
    return undefined;
  }
 
  const eventModule = events[eventName as keyof typeof events];
  Iif (!hasBaseSchema(eventModule) || !hasShape(eventModule.baseSchema)) {
    return undefined;
  }
 
  const attachmentsSchema = (eventModule.baseSchema.shape as Record<string, unknown>).attachments;
  if (!hasShape(attachmentsSchema)) {
    return undefined;
  }
 
  return attachmentsSchema.shape;
};
 
export const getDraftAttachmentKeyOrder = (eventName?: opensearch.main.Document["event"]) =>
  Object.keys(getDraftAttachmentShape(eventName) ?? {});
 
export const getDraftAttachmentDefaultLabel = (
  eventName: opensearch.main.Document["event"] | undefined,
  attachmentKey: string,
) => {
  const attachmentShape = getDraftAttachmentShape(eventName);
  if (!attachmentShape) {
    return undefined;
  }
 
  const attachmentSchema = attachmentShape[attachmentKey];
  Iif (!hasShape(attachmentSchema)) {
    return undefined;
  }
 
  const labelSchema = attachmentSchema.shape.label;
  Iif (!hasSafeParse(labelSchema)) {
    return undefined;
  }
 
  const labelResult = labelSchema.safeParse(undefined);
  return labelResult.success &&
    typeof labelResult.data === "string" &&
    labelResult.data.trim().length > 0
    ? labelResult.data.trim()
    : undefined;
};
 
const humanizeDraftAttachmentKey = (key: string) =>
  key.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/\b\w/g, (char) => char.toUpperCase());
 
export const getDraftAttachments = (
  submission?: opensearch.main.Document,
): NonNullable<opensearch.changelog.Document["attachments"]> => {
  const attachmentSections = (submission?.draft?.data as Record<string, unknown> | undefined)
    ?.attachments;
 
  if (!attachmentSections || typeof attachmentSections !== "object") {
    return [];
  }
 
  const attachmentKeyOrder = getDraftAttachmentKeyOrder(submission?.event);
  const attachmentKeyOrderIndex = new Map(
    attachmentKeyOrder.map((attachmentKey, index) => [attachmentKey, index]),
  );
  const orderedAttachmentEntries = Object.entries(attachmentSections)
    .map(([attachmentKey, attachmentSection], originalIndex) => ({
      attachmentKey,
      attachmentSection,
      originalIndex,
      sortIndex: attachmentKeyOrderIndex.get(attachmentKey) ?? Number.MAX_SAFE_INTEGER,
    }))
    .sort((left, right) => {
      if (left.sortIndex !== right.sortIndex) {
        return left.sortIndex - right.sortIndex;
      }
 
      return left.originalIndex - right.originalIndex;
    });
 
  return orderedAttachmentEntries.flatMap(({ attachmentKey, attachmentSection }) => {
    Iif (!attachmentSection || typeof attachmentSection !== "object") {
      return [];
    }
 
    const sectionLabel =
      typeof (attachmentSection as { label?: unknown }).label === "string" &&
      (attachmentSection as { label?: string }).label?.trim()
        ? (attachmentSection as { label: string }).label.trim()
        : (getDraftAttachmentDefaultLabel(submission?.event, attachmentKey) ??
          humanizeDraftAttachmentKey(attachmentKey));
 
    const files = (attachmentSection as { files?: unknown }).files;
    Iif (!Array.isArray(files) || files.length === 0) {
      return [];
    }
 
    return files.flatMap((file) => {
      Iif (!file || typeof file !== "object") {
        return [];
      }
 
      const maybeAttachment = file as Record<string, unknown>;
      const filename =
        typeof maybeAttachment.filename === "string" ? maybeAttachment.filename : undefined;
      const bucket =
        typeof maybeAttachment.bucket === "string" ? maybeAttachment.bucket : undefined;
      const key = typeof maybeAttachment.key === "string" ? maybeAttachment.key : undefined;
      const uploadDate =
        typeof maybeAttachment.uploadDate === "number" ? maybeAttachment.uploadDate : undefined;
      Iif (!filename || !bucket || !key || typeof uploadDate !== "number") {
        return [];
      }
 
      return [
        {
          filename,
          bucket,
          key,
          uploadDate,
          title: sectionLabel,
        },
      ];
    });
  });
};