@aspan-corporation/ac-shared 1.2.20 → 1.2.21

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,18 +1,18 @@
1
1
  import { IdempotencyConfig } from "@aws-lambda-powertools/idempotency";
2
+ import { BasePersistenceLayer } from "@aws-lambda-powertools/idempotency/persistence";
2
3
  import { Logger } from "@aws-lambda-powertools/logger";
3
4
  import { Metrics } from "@aws-lambda-powertools/metrics";
4
5
  import { Tracer } from "@aws-lambda-powertools/tracer";
5
6
  import middy from "@middy/core";
6
- import type { Handler, SQSHandler } from "aws-lambda";
7
+ import type { Context, Handler, SQSRecord } from "aws-lambda";
7
8
  import { DynamoDBService, LocationService, S3Service, SQSService, SSMService, STSService } from "../services/index.js";
8
- import { BasePersistenceLayer } from "@aws-lambda-powertools/idempotency/persistence";
9
9
  export declare const logger: Logger;
10
10
  export type { Logger } from "@aws-lambda-powertools/logger";
11
- export declare const tracer: Tracer;
11
+ export { MetricUnit } from "@aws-lambda-powertools/metrics";
12
+ export type { Metrics } from "@aws-lambda-powertools/metrics";
12
13
  export type { Tracer } from "@aws-lambda-powertools/tracer";
14
+ export declare const tracer: Tracer;
13
15
  export declare const metrics: Metrics;
14
- export type { Metrics } from "@aws-lambda-powertools/metrics";
15
- export { MetricUnit } from "@aws-lambda-powertools/metrics";
16
16
  export declare const withMiddlewares: (handler: Handler) => middy.MiddyfiedHandler<any, any, Error, any>;
17
17
  export declare const getPersistenceStore: (tableName: string) => BasePersistenceLayer;
18
18
  export declare const getIdempotencyConfig: (eventKeyJmesPath: string) => IdempotencyConfig;
@@ -30,13 +30,12 @@ export type AcServices = {
30
30
  sourceS3Service?: S3Service;
31
31
  destinationS3Service?: S3Service;
32
32
  };
33
- declare module "aws-lambda" {
34
- interface Context {
35
- logger: Logger;
36
- tracer: Tracer;
37
- metrics: Metrics;
38
- acServices?: AcServices;
39
- }
40
- }
41
- export declare const getPartialResponseHandler: (handler: SQSHandler) => (event: any, context: any) => Promise<import("@aws-lambda-powertools/batch/types").PartialItemFailureResponse>;
33
+ export type AcContext = Context & {
34
+ logger: Logger;
35
+ tracer: Tracer;
36
+ metrics: Metrics;
37
+ acServices?: AcServices;
38
+ };
39
+ type RecordHandler = (record: SQSRecord, context: AcContext) => Promise<void>;
40
+ export declare const getPartialResponseHandler: (handler: RecordHandler) => (event: any, context: any) => Promise<import("@aws-lambda-powertools/batch/types").PartialItemFailureResponse>;
42
41
  export { makeIdempotent } from "@aws-lambda-powertools/idempotency";
@@ -0,0 +1,56 @@
1
+ import { BatchProcessor, EventType, processPartialResponse } from "@aws-lambda-powertools/batch";
2
+ import { IdempotencyConfig } from "@aws-lambda-powertools/idempotency";
3
+ import { DynamoDBPersistenceLayer } from "@aws-lambda-powertools/idempotency/dynamodb";
4
+ import { Logger } from "@aws-lambda-powertools/logger";
5
+ import { injectLambdaContext } from "@aws-lambda-powertools/logger/middleware";
6
+ import { Metrics, MetricUnit } from "@aws-lambda-powertools/metrics";
7
+ import { logMetrics } from "@aws-lambda-powertools/metrics/middleware";
8
+ import { Tracer } from "@aws-lambda-powertools/tracer";
9
+ import { captureLambdaHandler } from "@aws-lambda-powertools/tracer/middleware";
10
+ import middy from "@middy/core";
11
+ import { randomUUID } from "node:crypto";
12
+ export const logger = new Logger();
13
+ export { MetricUnit } from "@aws-lambda-powertools/metrics";
14
+ export const tracer = new Tracer();
15
+ export const metrics = new Metrics({ namespace: "aspan-corporation" });
16
+ export const withMiddlewares = (handler) => middy(handler)
17
+ .use(injectLambdaContext(logger, { logEvent: true }))
18
+ .use(captureLambdaHandler(tracer))
19
+ .use(logMetrics(metrics, { captureColdStartMetric: true }))
20
+ .use({
21
+ before: async (request) => {
22
+ if (!logger.getCorrelationId()) {
23
+ logger.setCorrelationId(request.context.awsRequestId || randomUUID());
24
+ }
25
+ request.context.logger = logger;
26
+ request.context.tracer = tracer;
27
+ request.context.metrics = metrics;
28
+ }
29
+ })
30
+ .use({
31
+ onError: async ({ error, context: { logger, metrics } }) => {
32
+ logger.error("Error handled by middleware", error);
33
+ metrics.addMetric("ErrorHandled", MetricUnit.Count, 1);
34
+ }
35
+ });
36
+ export const getPersistenceStore = (tableName) => {
37
+ return new DynamoDBPersistenceLayer({
38
+ tableName
39
+ });
40
+ };
41
+ export const getIdempotencyConfig = (eventKeyJmesPath) => {
42
+ return new IdempotencyConfig({
43
+ eventKeyJmesPath
44
+ });
45
+ };
46
+ export const getIdempotencyOptions = (tableName, path) => ({
47
+ persistenceStore: getPersistenceStore(tableName),
48
+ config: getIdempotencyConfig(path)
49
+ });
50
+ export const getPartialResponseHandler = (handler) => {
51
+ const processor = new BatchProcessor(EventType.SQS);
52
+ return async (event, context) => processPartialResponse(event, handler, processor, {
53
+ context
54
+ });
55
+ };
56
+ export { makeIdempotent } from "@aws-lambda-powertools/idempotency";
@@ -0,0 +1,30 @@
1
+ import { CloudWatchLogsClient, DescribeLogStreamsCommand, PutLogEventsCommand } from "@aws-sdk/client-cloudwatch-logs";
2
+ import assert from "node:assert/strict";
3
+ import { getObjectWithAssumeRoleCommandOutputAttribute } from "../utils/index.js";
4
+ export class CloudWatchService {
5
+ logger;
6
+ client;
7
+ constructor({ logger, client, assumeRoleCommandOutput, region }) {
8
+ this.logger = logger;
9
+ if (client) {
10
+ this.client = client;
11
+ }
12
+ else {
13
+ assert(region, "Region must be provided if client is not passed");
14
+ this.client = new CloudWatchLogsClient({
15
+ region,
16
+ ...getObjectWithAssumeRoleCommandOutputAttribute(assumeRoleCommandOutput)
17
+ });
18
+ }
19
+ }
20
+ async describeLogStreamsCommand(describeLogStreamsCommandInput) {
21
+ this.logger.debug("describeLogStreamsCommand", {
22
+ describeLogStreamsCommandInput
23
+ });
24
+ return await this.client.send(new DescribeLogStreamsCommand(describeLogStreamsCommandInput));
25
+ }
26
+ async putLogEventsCommand(putLogEventsCommandInput) {
27
+ this.logger.debug("putLogEventsCommand", { putLogEventsCommandInput });
28
+ return await this.client.send(new PutLogEventsCommand(putLogEventsCommandInput));
29
+ }
30
+ }
@@ -0,0 +1,45 @@
1
+ import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
2
+ import { DynamoDBDocumentClient, GetCommand, PutCommand, QueryCommand, UpdateCommand, ScanCommand, BatchWriteCommand } from "@aws-sdk/lib-dynamodb";
3
+ import assert from "node:assert/strict";
4
+ import { getObjectWithAssumeRoleCommandOutputAttribute } from "../utils/index.js";
5
+ export class DynamoDBService {
6
+ logger;
7
+ client;
8
+ documentClient;
9
+ constructor({ logger, client, assumeRoleCommandOutput, region }) {
10
+ this.logger = logger;
11
+ if (client) {
12
+ this.client = client;
13
+ }
14
+ else {
15
+ assert(region, "Region must be provided if client is not passed");
16
+ this.client = new DynamoDBClient({
17
+ region,
18
+ ...getObjectWithAssumeRoleCommandOutputAttribute(assumeRoleCommandOutput)
19
+ });
20
+ }
21
+ this.documentClient = DynamoDBDocumentClient.from(this.client);
22
+ }
23
+ async getCommand(getCommandInput) {
24
+ return await this.documentClient.send(new GetCommand(getCommandInput));
25
+ }
26
+ async updateCommand(updateCommandInput) {
27
+ return await this.documentClient.send(new UpdateCommand(updateCommandInput));
28
+ }
29
+ async queryCommand(queryCommandInput) {
30
+ return await this.documentClient.send(new QueryCommand(queryCommandInput));
31
+ }
32
+ async putCommand(putCommandInput) {
33
+ return await this.documentClient.send(new PutCommand(putCommandInput));
34
+ }
35
+ async checkIfItemExists(checkInput) {
36
+ const result = await this.getCommand(checkInput);
37
+ return !!result.Item;
38
+ }
39
+ async scanCommand(scanCommandInput) {
40
+ return await this.client.send(new ScanCommand(scanCommandInput));
41
+ }
42
+ async batchWriteCommand(batchWriteCommandInput) {
43
+ return await this.client.send(new BatchWriteCommand(batchWriteCommandInput));
44
+ }
45
+ }
@@ -0,0 +1,7 @@
1
+ export * from "./s3.js";
2
+ export * from "./dynamoDB.js";
3
+ export * from "./ssm.js";
4
+ export * from "./sqs.js";
5
+ export * from "./sts.js";
6
+ export * from "./location.js";
7
+ export * from "./cloudWatch.js";
@@ -0,0 +1,23 @@
1
+ import { LocationClient, SearchPlaceIndexForPositionCommand } from "@aws-sdk/client-location";
2
+ import assert from "node:assert/strict";
3
+ import { getObjectWithAssumeRoleCommandOutputAttribute } from "../utils/index.js";
4
+ export class LocationService {
5
+ logger;
6
+ client;
7
+ constructor({ logger, client, assumeRoleCommandOutput, region }) {
8
+ this.logger = logger;
9
+ if (client) {
10
+ this.client = client;
11
+ }
12
+ else {
13
+ assert(region, "Region must be provided if client is not passed");
14
+ this.client = new LocationClient({
15
+ region,
16
+ ...getObjectWithAssumeRoleCommandOutputAttribute(assumeRoleCommandOutput)
17
+ });
18
+ }
19
+ }
20
+ async searchPlaceIndexForPositionCommand(searchPlaceIndexForPositionCommandInput) {
21
+ return await this.client.send(new SearchPlaceIndexForPositionCommand(searchPlaceIndexForPositionCommandInput));
22
+ }
23
+ }
@@ -0,0 +1,92 @@
1
+ import { DeleteObjectCommand, DeleteObjectsCommand, GetObjectCommand, HeadObjectCommand, ListObjectsV2Command, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
2
+ import { Upload } from "@aws-sdk/lib-storage";
3
+ import assert from "node:assert/strict";
4
+ import { PassThrough } from "node:stream";
5
+ import { getObjectWithAssumeRoleCommandOutputAttribute } from "../utils/index.js";
6
+ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
7
+ export class S3Service {
8
+ logger;
9
+ client;
10
+ constructor({ logger, client, assumeRoleCommandOutput, region }) {
11
+ this.logger = logger;
12
+ if (client) {
13
+ this.client = client;
14
+ }
15
+ else {
16
+ assert(region, "Region must be provided if client is not passed");
17
+ this.client = new S3Client({
18
+ region,
19
+ ...getObjectWithAssumeRoleCommandOutputAttribute(assumeRoleCommandOutput)
20
+ });
21
+ }
22
+ }
23
+ createS3UploadStream(putObjectCommandInput) {
24
+ this.logger.debug("upload stream started", { putObjectCommandInput });
25
+ const pass = new PassThrough();
26
+ const parallelUploads = new Upload({
27
+ client: this.client,
28
+ params: {
29
+ ...putObjectCommandInput,
30
+ Body: pass
31
+ }
32
+ });
33
+ const donePromise = parallelUploads
34
+ .done()
35
+ .then(() => this.logger.debug("upload stream finished"))
36
+ .catch((err) => {
37
+ this.logger.error("upload stream error", err);
38
+ pass.destroy(err);
39
+ throw err;
40
+ });
41
+ return { stream: pass, done: donePromise };
42
+ }
43
+ async createS3DownloadStream(getObjectCommandInput) {
44
+ this.logger.debug("download stream started", { getObjectCommandInput });
45
+ const item = await this.client.send(new GetObjectCommand(getObjectCommandInput));
46
+ this.logger.debug("download stream finished");
47
+ return item.Body;
48
+ }
49
+ async listObjectsV2(listObjectsV2CommandInput) {
50
+ this.logger.debug("listObjectsV2", { listObjectsV2CommandInput });
51
+ return await this.client.send(new ListObjectsV2Command(listObjectsV2CommandInput));
52
+ }
53
+ async getObject(getObjectCommandInput) {
54
+ this.logger.debug("getObject", { getObjectCommandInput });
55
+ const stream = await this.createS3DownloadStream(getObjectCommandInput);
56
+ return Buffer.from(await stream.transformToByteArray());
57
+ }
58
+ async getSignedUrl(getObjectCommandInput) {
59
+ this.logger.debug("getSignedUrl", { getObjectCommandInput });
60
+ const signedUrl = await getSignedUrl(this.client, new GetObjectCommand(getObjectCommandInput), { expiresIn: 3600 });
61
+ return signedUrl;
62
+ }
63
+ async putObject(putObjectCommandInput) {
64
+ this.logger.debug("putObject", { putObjectCommandInput });
65
+ return await this.client.send(new PutObjectCommand(putObjectCommandInput));
66
+ }
67
+ async headObject(headObjectCommandInput) {
68
+ this.logger.debug("headObject", { headObjectCommandInput });
69
+ return await this.client.send(new HeadObjectCommand(headObjectCommandInput));
70
+ }
71
+ async checkIfObjectExists(headObjectCommandInput) {
72
+ this.logger.debug("checkIfObjectExists", { headObjectCommandInput });
73
+ try {
74
+ await this.headObject(headObjectCommandInput);
75
+ return true;
76
+ }
77
+ catch (error) {
78
+ if (error.name === "NotFound") {
79
+ return false;
80
+ }
81
+ throw error;
82
+ }
83
+ }
84
+ async deleteObject(deleteObjectCommandInput) {
85
+ this.logger.debug("deleteObject", { deleteObjectCommandInput });
86
+ return await this.client.send(new DeleteObjectCommand(deleteObjectCommandInput));
87
+ }
88
+ async deleteObjects(deleteObjectsCommandInput) {
89
+ this.logger.debug("deleteObjects", { deleteObjectsCommandInput });
90
+ return await this.client.send(new DeleteObjectsCommand(deleteObjectsCommandInput));
91
+ }
92
+ }
@@ -0,0 +1,32 @@
1
+ import { DeleteMessageCommand, ReceiveMessageCommand, SQSClient, SendMessageBatchCommand, SendMessageCommand } from "@aws-sdk/client-sqs";
2
+ import assert from "node:assert/strict";
3
+ import { getObjectWithAssumeRoleCommandOutputAttribute } from "../utils/index.js";
4
+ export class SQSService {
5
+ logger;
6
+ client;
7
+ constructor({ logger, client, assumeRoleCommandOutput, region }) {
8
+ this.logger = logger;
9
+ if (client) {
10
+ this.client = client;
11
+ }
12
+ else {
13
+ assert(region, "Region must be provided if client is not passed");
14
+ this.client = new SQSClient({
15
+ region,
16
+ ...getObjectWithAssumeRoleCommandOutputAttribute(assumeRoleCommandOutput)
17
+ });
18
+ }
19
+ }
20
+ async sendMessage(params) {
21
+ return await this.client.send(new SendMessageCommand(params));
22
+ }
23
+ async sendMessageBatch(params) {
24
+ return await this.client.send(new SendMessageBatchCommand(params));
25
+ }
26
+ async receiveMessage(params) {
27
+ return await this.client.send(new ReceiveMessageCommand(params));
28
+ }
29
+ async deleteMessage(params) {
30
+ return await this.client.send(new DeleteMessageCommand(params));
31
+ }
32
+ }
@@ -0,0 +1,26 @@
1
+ import { GetParameterCommand, PutParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
2
+ import assert from "node:assert/strict";
3
+ import { getObjectWithAssumeRoleCommandOutputAttribute } from "../utils/index.js";
4
+ export class SSMService {
5
+ logger;
6
+ client;
7
+ constructor({ logger, client, assumeRoleCommandOutput, region }) {
8
+ this.logger = logger;
9
+ if (client) {
10
+ this.client = client;
11
+ }
12
+ else {
13
+ assert(region, "Region must be provided if client is not passed");
14
+ this.client = new SSMClient({
15
+ region,
16
+ ...getObjectWithAssumeRoleCommandOutputAttribute(assumeRoleCommandOutput)
17
+ });
18
+ }
19
+ }
20
+ async getParameter(params) {
21
+ return await this.client.send(new GetParameterCommand(params));
22
+ }
23
+ async putParameter(params) {
24
+ return await this.client.send(new PutParameterCommand(params));
25
+ }
26
+ }
@@ -0,0 +1,33 @@
1
+ import { Logger } from "@aws-lambda-powertools/logger";
2
+ import { AssumeRoleCommand, GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts";
3
+ import assert from "node:assert/strict";
4
+ export class STSService {
5
+ logger;
6
+ client;
7
+ constructor({ logger, region, client }) {
8
+ if (logger) {
9
+ this.logger = logger;
10
+ }
11
+ else {
12
+ this.logger = new Logger();
13
+ this.logger.appendKeys({ scope: "STSService" });
14
+ }
15
+ if (client) {
16
+ this.client = client;
17
+ }
18
+ else {
19
+ assert(region, "Region must be provided if client is not passed");
20
+ this.client = new STSClient({
21
+ region
22
+ });
23
+ }
24
+ }
25
+ async getCallerIdentity() {
26
+ this.logger.debug("getCallerIdentity");
27
+ return await this.client.send(new GetCallerIdentityCommand({}));
28
+ }
29
+ async assumeRole(assumeRoleCommandInput) {
30
+ this.logger.debug("assumeRole", { assumeRoleCommandInput });
31
+ return await this.client.send(new AssumeRoleCommand(assumeRoleCommandInput));
32
+ }
33
+ }
@@ -0,0 +1,10 @@
1
+ export const getObjectWithAssumeRoleCommandOutputAttribute = (assumeRoleCommandOutput) => assumeRoleCommandOutput
2
+ ? {
3
+ credentials: {
4
+ accessKeyId: assumeRoleCommandOutput?.Credentials?.AccessKeyId,
5
+ secretAccessKey: assumeRoleCommandOutput?.Credentials?.SecretAccessKey,
6
+ sessionToken: assumeRoleCommandOutput?.Credentials?.SessionToken
7
+ }
8
+ }
9
+ : {};
10
+ export const getObjectWithStackAttribute = (error) => error && error instanceof Error ? { stack: error.stack } : {};
@@ -0,0 +1,9 @@
1
+ import assert from "node:assert/strict";
2
+ export const assertEnvVar = (envVar) => {
3
+ assert(process.env[envVar], `${envVar} is not set`);
4
+ return process.env[envVar];
5
+ };
6
+ export * from "./normalizeErrorMessage.js";
7
+ export * from "./thumbsKey.js";
8
+ export * from "./helpers.js";
9
+ export * from "./processMeta.js";
@@ -0,0 +1,14 @@
1
+ export const normalizeErrorMessage = (error) => {
2
+ if (error instanceof Error) {
3
+ return error.message;
4
+ }
5
+ else if (typeof error === "string") {
6
+ return error;
7
+ }
8
+ else if (typeof error === "object" && error !== null) {
9
+ return JSON.stringify(error);
10
+ }
11
+ else {
12
+ return String(error);
13
+ }
14
+ };
@@ -0,0 +1,135 @@
1
+ import assert from "node:assert/strict";
2
+ import { getKeyExtension } from "./thumbsKey.js";
3
+ /**
4
+ * 1. read <id>
5
+ * 2. if <id> doesn't exist then use PutCommand
6
+ * 3. if it does exist then match new tags and old tags
7
+ * 4. add all old tags that are not present in new tags,
8
+ *
9
+ */
10
+ export const processMeta = async ({ id, meta, metaTableName, size, logger, locationService, dynamoDBService }) => {
11
+ // expand location data
12
+ const latitude = Number(meta.find((tag) => tag.key === "latitude")?.value);
13
+ const longitude = Number(meta.find((tag) => tag.key === "longitude")?.value);
14
+ let geoPositionResult;
15
+ if (!Number.isNaN(longitude) && !Number.isNaN(latitude)) {
16
+ try {
17
+ const res = await locationService.searchPlaceIndexForPositionCommand({
18
+ IndexName: "TauPlaceIndex",
19
+ Position: [longitude, latitude]
20
+ });
21
+ if (res?.Results && res?.Results?.length > 0) {
22
+ geoPositionResult = res?.Results[0];
23
+ }
24
+ else {
25
+ throw Error("Can not resolve geo data");
26
+ }
27
+ }
28
+ catch (error) {
29
+ logger.error("Error fetching geo data", {
30
+ message: error instanceof Error ? error.message : ""
31
+ });
32
+ }
33
+ }
34
+ const newTags = [
35
+ ...meta,
36
+ ...(meta.find((tag) => tag.key === "dateCreated")
37
+ ? []
38
+ : extractMetaFromKey(id)),
39
+ { key: "extension", value: getKeyExtension(id) },
40
+ { key: "size", value: String(size) },
41
+ { key: "sizeMb", value: String(Math.round(size / 1024 ** 2)) },
42
+ ...(geoPositionResult?.Place?.Label
43
+ ? [{ key: "label", value: geoPositionResult?.Place?.Label }]
44
+ : []),
45
+ ...(geoPositionResult?.Place?.Country
46
+ ? [{ key: "country", value: geoPositionResult?.Place?.Country }]
47
+ : []),
48
+ ...(geoPositionResult?.Place?.Region
49
+ ? [{ key: "region", value: geoPositionResult?.Place?.Region }]
50
+ : []),
51
+ ...(geoPositionResult?.Place?.SubRegion
52
+ ? [{ key: "subRegion", value: geoPositionResult?.Place?.SubRegion }]
53
+ : []),
54
+ ...(geoPositionResult?.Place?.Municipality
55
+ ? [{ key: "municipality", value: geoPositionResult?.Place?.Municipality }]
56
+ : []),
57
+ ...(geoPositionResult?.Place?.Neighborhood
58
+ ? [{ key: "neighborhood", value: geoPositionResult?.Place?.Neighborhood }]
59
+ : []),
60
+ ...(geoPositionResult?.Place?.PostalCode
61
+ ? [{ key: "postalCode", value: geoPositionResult?.Place?.PostalCode }]
62
+ : [])
63
+ ].map((tag) => ({
64
+ key: "ac:tau:" + tag.key,
65
+ value: tag.value
66
+ }));
67
+ logger.debug("metaData", { newTags });
68
+ logger.debug("trying to read existing metadata");
69
+ const getResponse = await dynamoDBService.getCommand({
70
+ TableName: metaTableName,
71
+ Key: {
72
+ id
73
+ }
74
+ });
75
+ const oldTags = getResponse.Item?.tags;
76
+ logger.debug("existing tags", { getResponse });
77
+ const reconciledTags = reconcileTags({ newTags, oldTags }, logger);
78
+ logger.debug("result", { reconciledTags });
79
+ const updateResponse = await dynamoDBService.updateCommand({
80
+ TableName: metaTableName,
81
+ Key: {
82
+ id
83
+ },
84
+ UpdateExpression: "set tags = :tags",
85
+ ExpressionAttributeValues: {
86
+ ":tags": reconciledTags
87
+ },
88
+ ReturnValues: "ALL_NEW"
89
+ });
90
+ logger.debug("sent UpdateCommand", { updateResponse });
91
+ };
92
+ const reconcileTags = ({ oldTags = [], newTags = [] }, logger) => oldTags.reduce((acc, cur) => {
93
+ if (acc.find((element) => element.key === cur.key)) {
94
+ return acc;
95
+ }
96
+ else {
97
+ logger.debug("added", cur);
98
+ return [...acc, cur];
99
+ }
100
+ }, newTags);
101
+ const SUBSTRING_ANSI_DATES_BEGIN_WITH = "20";
102
+ const extractMetaFromKey = (key) => {
103
+ if (!key)
104
+ return [];
105
+ let firstToken = undefined;
106
+ try {
107
+ const folder = key.split("/").at(-2);
108
+ assert(typeof folder === "string");
109
+ firstToken = folder.split(".")[0];
110
+ }
111
+ catch (error) { }
112
+ if (firstToken === undefined ||
113
+ firstToken.length !== 8 ||
114
+ !firstToken.startsWith(SUBSTRING_ANSI_DATES_BEGIN_WITH))
115
+ return [];
116
+ const year = Number(firstToken.substring(0, 4));
117
+ const month = Number(firstToken.substring(4, 6)) - 1;
118
+ const day = Number(firstToken.substring(6));
119
+ if (isNaN(year) || isNaN(month) || isNaN(day))
120
+ return [];
121
+ // mon starts from 0
122
+ // day starts from 1
123
+ if (year < 2000 || month < 1 || month > 11 || day < 1 || day > 31)
124
+ return [];
125
+ const dateCreatedBin = new Date(year, month, day);
126
+ return [
127
+ { key: "dateCreated", value: dateCreatedBin.toISOString() },
128
+ { key: "yearCreated", value: dateCreatedBin.getFullYear().toString() },
129
+ { key: "dayCreated", value: dateCreatedBin.getDate().toString() },
130
+ {
131
+ key: "monthCreated",
132
+ value: (dateCreatedBin.getMonth() + 1).toString()
133
+ }
134
+ ];
135
+ };
@@ -1,13 +1,17 @@
1
- export declare const JPEG_EXTENSION = "jpeg";
2
- export declare const JPG_EXTENSION = "jpg";
3
- export declare const HEIC_EXTENSION = "heic";
4
- export declare const MOV_EXTENSION = "mov";
5
- export declare const THUMBS_EXTENSION = "jpg";
6
- export declare const ENCODED_VIDEO_EXTENSION = "mp4";
1
+ export declare const EXTENSION_JPEG = "jpeg";
2
+ export declare const EXTENSION_JPG = "jpg";
3
+ export declare const EXTENSION_WEBP = "webp";
4
+ export declare const EXTENSION_HEIC = "heic";
5
+ export declare const EXTENSION_MOV = "mov";
6
+ export declare const EXTENSION_THUMBS = "webp";
7
+ export declare const EXTENSION_ENCODED_VIDEO = "mp4";
7
8
  export declare const ALLOWED_EXTENSIONS: string[];
8
9
  export declare const ALLOWED_VIDEO_EXTENSIONS: string[];
9
- export declare const THUMBNAIL_RESOLUTIONS: number[][];
10
- export declare const getThumbsKey: ({ key, width, height }: {
10
+ export declare const DIM_THUMBNAIL_WIDTH = 320;
11
+ export declare const DIM_THUMBNAIL_HEIGHT = 320;
12
+ export declare const DIM_DETAIL_WIDTH = 1180;
13
+ export declare const DIM_DETAIL_HEIGHT = 820;
14
+ export declare const getThumbnailKey: ({ key, width, height }: {
11
15
  key: string;
12
16
  width: number;
13
17
  height: number;
@@ -0,0 +1,42 @@
1
+ import { extname, basename, dirname } from "node:path";
2
+ export const EXTENSION_JPEG = "jpeg";
3
+ export const EXTENSION_JPG = "jpg";
4
+ export const EXTENSION_WEBP = "webp";
5
+ export const EXTENSION_HEIC = "heic";
6
+ export const EXTENSION_MOV = "mov";
7
+ export const EXTENSION_THUMBS = EXTENSION_WEBP;
8
+ export const EXTENSION_ENCODED_VIDEO = "mp4";
9
+ export const ALLOWED_EXTENSIONS = [
10
+ EXTENSION_JPEG,
11
+ EXTENSION_JPG,
12
+ EXTENSION_HEIC
13
+ ];
14
+ export const ALLOWED_VIDEO_EXTENSIONS = [EXTENSION_MOV];
15
+ export const DIM_THUMBNAIL_WIDTH = 320;
16
+ export const DIM_THUMBNAIL_HEIGHT = 320;
17
+ export const DIM_DETAIL_WIDTH = 1180;
18
+ export const DIM_DETAIL_HEIGHT = 820;
19
+ const getPathNameWithoutExt = (key) => {
20
+ return dirname(key) + "/" + basename(key, extname(key));
21
+ };
22
+ export const getThumbnailKey = ({ key, width, height }) => {
23
+ return (getPathNameWithoutExt(key) +
24
+ "." +
25
+ width +
26
+ "x" +
27
+ height +
28
+ "." +
29
+ EXTENSION_THUMBS);
30
+ };
31
+ export const getEncodedVideoKey = ({ key }) => {
32
+ return getPathNameWithoutExt(key) + "." + EXTENSION_ENCODED_VIDEO;
33
+ };
34
+ export const getKeyExtension = (key) => extname(key).slice(1).toLowerCase();
35
+ export const isAllowedExtension = (key) => {
36
+ const ext = getKeyExtension(key);
37
+ return ALLOWED_EXTENSIONS.includes(ext);
38
+ };
39
+ export const isAllowedVideoExtension = (key) => {
40
+ const ext = getKeyExtension(key);
41
+ return ALLOWED_VIDEO_EXTENSIONS.includes(ext);
42
+ };