@aspan-corporation/ac-shared 1.2.39 → 1.2.40

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.
@@ -1,5 +1,15 @@
1
1
  import { DynamoDBService, LocationService } from "../services/index.js";
2
2
  import { Logger } from "@aws-lambda-powertools/logger";
3
+ /** Indexed tag holding the normalized media type of a meta record. */
4
+ export declare const TAG_MEDIA_TYPE = "ac:tau:type";
5
+ /**
6
+ * Normalizes a file extension into the coarse media type used for search
7
+ * filtering. Mirrors the allowlists that already gate pipeline dispatch
8
+ * (thumbsKey.ts's image/video lists, diary.ts's audio list) — kept as a
9
+ * single derivation point so the index tag and the dispatch gating never
10
+ * disagree about what an extension is.
11
+ */
12
+ export declare const deriveMediaType: (extension: string) => "photo" | "video" | "audio" | undefined;
3
13
  type ExtractMetadataParams = {
4
14
  id: string;
5
15
  meta: Array<{
@@ -20,7 +30,7 @@ type ExtractMetadataParams = {
20
30
  * 4. add all old tags that are not present in new tags,
21
31
  *
22
32
  */
23
- export declare const processMeta: ({ id, meta, metaTableName, placeIndexName, size, logger, locationService, dynamoDBService }: ExtractMetadataParams) => Promise<void>;
33
+ export declare const processMeta: ({ id, meta, metaTableName, placeIndexName, size, logger, locationService, dynamoDBService, }: ExtractMetadataParams) => Promise<void>;
24
34
  /**
25
35
  * Parent folder of an S3 key, used as the `folder` GSI partition key on the meta table.
26
36
  *
@@ -1,5 +1,24 @@
1
- import { getKeyExtension } from "./thumbsKey.js";
1
+ import { ALLOWED_EXTENSIONS, ALLOWED_VIDEO_EXTENSIONS, getKeyExtension, } from "./thumbsKey.js";
2
+ import { AUDIO_EXTENSIONS } from "./diary.js";
2
3
  import { parseFolderDate } from "./parseFolderDate.js";
4
+ /** Indexed tag holding the normalized media type of a meta record. */
5
+ export const TAG_MEDIA_TYPE = "ac:tau:type";
6
+ /**
7
+ * Normalizes a file extension into the coarse media type used for search
8
+ * filtering. Mirrors the allowlists that already gate pipeline dispatch
9
+ * (thumbsKey.ts's image/video lists, diary.ts's audio list) — kept as a
10
+ * single derivation point so the index tag and the dispatch gating never
11
+ * disagree about what an extension is.
12
+ */
13
+ export const deriveMediaType = (extension) => {
14
+ if (ALLOWED_VIDEO_EXTENSIONS.includes(extension))
15
+ return "video";
16
+ if (AUDIO_EXTENSIONS.includes(extension))
17
+ return "audio";
18
+ if (ALLOWED_EXTENSIONS.includes(extension))
19
+ return "photo";
20
+ return undefined;
21
+ };
3
22
  /**
4
23
  * 1. read <id>
5
24
  * 2. if <id> doesn't exist then use PutCommand
@@ -7,7 +26,7 @@ import { parseFolderDate } from "./parseFolderDate.js";
7
26
  * 4. add all old tags that are not present in new tags,
8
27
  *
9
28
  */
10
- export const processMeta = async ({ id, meta, metaTableName, placeIndexName, size, logger, locationService, dynamoDBService }) => {
29
+ export const processMeta = async ({ id, meta, metaTableName, placeIndexName, size, logger, locationService, dynamoDBService, }) => {
11
30
  // expand location data
12
31
  const latitude = Number(meta.find((tag) => tag.key === "latitude")?.value);
13
32
  const longitude = Number(meta.find((tag) => tag.key === "longitude")?.value);
@@ -16,7 +35,7 @@ export const processMeta = async ({ id, meta, metaTableName, placeIndexName, siz
16
35
  try {
17
36
  const res = await locationService.searchPlaceIndexForPositionCommand({
18
37
  IndexName: placeIndexName,
19
- Position: [longitude, latitude]
38
+ Position: [longitude, latitude],
20
39
  });
21
40
  if (res?.Results && res?.Results?.length > 0) {
22
41
  geoPositionResult = res?.Results[0];
@@ -27,7 +46,7 @@ export const processMeta = async ({ id, meta, metaTableName, placeIndexName, siz
27
46
  }
28
47
  catch (error) {
29
48
  logger.error("Error fetching geo data", {
30
- message: error instanceof Error ? error.message : ""
49
+ message: error instanceof Error ? error.message : "",
31
50
  });
32
51
  }
33
52
  }
@@ -53,8 +72,12 @@ export const processMeta = async ({ id, meta, metaTableName, placeIndexName, siz
53
72
  // Date tags derived from the S3 key path (used when EXIF has no date).
54
73
  const keyDateTags = dateCreatedTag ? [] : extractMetaFromKey(id);
55
74
  // Sentinel tags — always written so the search index can filter by absence of data.
56
- const hasDate = !!(dateCreatedTag || extraDateTags.length > 0 || keyDateTags.length > 0);
75
+ const hasDate = !!(dateCreatedTag ||
76
+ extraDateTags.length > 0 ||
77
+ keyDateTags.length > 0);
57
78
  const hasLocation = !!geoPositionResult?.Place?.Country;
79
+ const extension = id.endsWith("/") ? "" : getKeyExtension(id);
80
+ const mediaType = deriveMediaType(extension);
58
81
  const newTags = [
59
82
  ...meta,
60
83
  ...extraDateTags,
@@ -62,9 +85,10 @@ export const processMeta = async ({ id, meta, metaTableName, placeIndexName, siz
62
85
  ...(id.endsWith("/")
63
86
  ? []
64
87
  : [
65
- { key: "extension", value: getKeyExtension(id) },
88
+ { key: "extension", value: extension },
66
89
  { key: "size", value: String(size) },
67
90
  { key: "sizeMb", value: String(Math.round(size / 1024 ** 2)) },
91
+ ...(mediaType ? [{ key: "type", value: mediaType }] : []),
68
92
  ]),
69
93
  ...(geoPositionResult?.Place?.Label
70
94
  ? [{ key: "label", value: geoPositionResult?.Place?.Label }]
@@ -91,15 +115,15 @@ export const processMeta = async ({ id, meta, metaTableName, placeIndexName, siz
91
115
  { key: "hasLocation", value: hasLocation ? "true" : "false" },
92
116
  ].map((tag) => ({
93
117
  key: "ac:tau:" + tag.key,
94
- value: tag.value
118
+ value: tag.value,
95
119
  }));
96
120
  logger.debug("metaData", { newTags });
97
121
  logger.debug("trying to read existing metadata");
98
122
  const getResponse = await dynamoDBService.getCommand({
99
123
  TableName: metaTableName,
100
124
  Key: {
101
- id
102
- }
125
+ id,
126
+ },
103
127
  });
104
128
  const oldTags = getResponse.Item?.tags;
105
129
  logger.debug("existing tags", { getResponse });
@@ -108,17 +132,17 @@ export const processMeta = async ({ id, meta, metaTableName, placeIndexName, siz
108
132
  const updateResponse = await dynamoDBService.updateCommand({
109
133
  TableName: metaTableName,
110
134
  Key: {
111
- id
135
+ id,
112
136
  },
113
137
  UpdateExpression: "set tags = :tags, #folder = :folder",
114
138
  ExpressionAttributeNames: {
115
- "#folder": "folder"
139
+ "#folder": "folder",
116
140
  },
117
141
  ExpressionAttributeValues: {
118
142
  ":tags": reconciledTags,
119
- ":folder": deriveFolder(id)
143
+ ":folder": deriveFolder(id),
120
144
  },
121
- ReturnValues: "ALL_NEW"
145
+ ReturnValues: "ALL_NEW",
122
146
  });
123
147
  logger.debug("sent UpdateCommand", { updateResponse });
124
148
  };
@@ -162,7 +186,9 @@ const extractMetaFromKey = (key) => {
162
186
  if (!key)
163
187
  return [];
164
188
  // Look at the parent folder, not the file itself.
165
- const parent = key.endsWith("/") ? key : key.slice(0, key.lastIndexOf("/") + 1);
189
+ const parent = key.endsWith("/")
190
+ ? key
191
+ : key.slice(0, key.lastIndexOf("/") + 1);
166
192
  const parsed = parseFolderDate(parent);
167
193
  if (!parsed)
168
194
  return [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aspan-corporation/ac-shared",
3
- "version": "1.2.39",
3
+ "version": "1.2.40",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "exports": {