Add event photo search backend

This commit is contained in:
Tanuja Pansare 2026-08-12 12:15:04 +05:30
parent 8cc36b2674
commit 49f9357f72
7 changed files with 595 additions and 218 deletions

392
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "1.0.0",
"dependencies": {
"@aws-sdk/client-bedrock-runtime": "^3.1030.0",
"@aws-sdk/client-rekognition": "^3.1101.0",
"@aws-sdk/client-s3": "^3.1012.0",
"@aws-sdk/lib-storage": "^3.1012.0",
"@hapi/hapi": "^21.3.0",
@ -313,6 +314,25 @@
"node": ">=20.0.0"
}
},
"node_modules/@aws-sdk/client-rekognition": {
"version": "3.1101.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-rekognition/-/client-rekognition-3.1101.0.tgz",
"integrity": "sha512-M+bb4PuyAk4WC5oxn65aFMP5mYcPM9CAVmwu4Fzd3fridCX8ElBVYBE8L4micpihtV6VvN1NQb8PzeT4qm244g==",
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "^3.977.4",
"@aws-sdk/credential-provider-node": "^3.972.76",
"@aws-sdk/types": "^3.974.2",
"@smithy/core": "^3.31.1",
"@smithy/fetch-http-handler": "^5.6.13",
"@smithy/node-http-handler": "^4.9.13",
"@smithy/types": "^4.16.1",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@aws-sdk/client-s3": {
"version": "3.1012.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1012.0.tgz",
@ -380,29 +400,33 @@
}
},
"node_modules/@aws-sdk/core": {
"version": "3.973.27",
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.27.tgz",
"integrity": "sha512-CUZ5m8hwMCH6OYI4Li/WgMfIEx10Q2PLI9Y3XOUTPGZJ53aZ0007jCv+X/ywsaERyKPdw5MRZWk877roQksQ4A==",
"version": "3.977.4",
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.977.4.tgz",
"integrity": "sha512-CEkcQlMOQJCvul60U7wdAOACjtdgFWDsfJI+6wUOGdhGNV2lGbuJpi/R50QLpFG3Tp+sQxa/RmzC3X7KHbhuTA==",
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/types": "^3.973.7",
"@aws-sdk/xml-builder": "^3.972.17",
"@smithy/core": "^3.23.14",
"@smithy/node-config-provider": "^4.3.13",
"@smithy/property-provider": "^4.2.13",
"@smithy/protocol-http": "^5.3.13",
"@smithy/signature-v4": "^5.3.13",
"@smithy/smithy-client": "^4.12.9",
"@smithy/types": "^4.14.0",
"@smithy/util-base64": "^4.3.2",
"@smithy/util-middleware": "^4.2.13",
"@smithy/util-utf8": "^4.2.2",
"@aws-sdk/types": "^3.974.2",
"@aws-sdk/xml-builder": "^3.972.37",
"@aws/lambda-invoke-store": "^0.3.0",
"@smithy/core": "^3.31.1",
"@smithy/signature-v4": "^5.6.12",
"@smithy/types": "^4.16.1",
"bowser": "^2.11.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@aws-sdk/core/node_modules/@aws/lambda-invoke-store": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz",
"integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@aws-sdk/crc64-nvme": {
"version": "3.972.5",
"resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.5.tgz",
@ -417,15 +441,15 @@
}
},
"node_modules/@aws-sdk/credential-provider-env": {
"version": "3.972.25",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.25.tgz",
"integrity": "sha512-6QfI0wv4jpG5CrdO/AO0JfZ2ux+tKwJPrUwmvxXF50vI5KIypKVGNF6b4vlkYEnKumDTI1NX2zUBi8JoU5QU3A==",
"version": "3.972.65",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.65.tgz",
"integrity": "sha512-lJT2aRw9wCV8jPHyFJjdZLD4HTydL6/22AnCSOB8e/LqOc55nEJGLHkJQeSxhn8QiqyjFwPKQFtMw0ovjRUY/g==",
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "^3.973.27",
"@aws-sdk/types": "^3.973.7",
"@smithy/property-provider": "^4.2.13",
"@smithy/types": "^4.14.0",
"@aws-sdk/core": "^3.977.4",
"@aws-sdk/types": "^3.974.2",
"@smithy/core": "^3.31.1",
"@smithy/types": "^4.16.1",
"tslib": "^2.6.2"
},
"engines": {
@ -433,20 +457,17 @@
}
},
"node_modules/@aws-sdk/credential-provider-http": {
"version": "3.972.27",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.27.tgz",
"integrity": "sha512-3V3Usj9Gs93h865DqN4M2NWJhC5kXU9BvZskfN3+69omuYlE3TZxOEcVQtBGLOloJB7BVfJKXVLqeNhOzHqSlQ==",
"version": "3.972.67",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.67.tgz",
"integrity": "sha512-N7fw/15hSwI/CPxe5ohOyb7O4ge9f5me1gVIn8OIkBRB0squ8OJqQyDyH/HoL+Sb1W5xdC88jVC+bHkw73iu+Q==",
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "^3.973.27",
"@aws-sdk/types": "^3.973.7",
"@smithy/fetch-http-handler": "^5.3.16",
"@smithy/node-http-handler": "^4.5.2",
"@smithy/property-provider": "^4.2.13",
"@smithy/protocol-http": "^5.3.13",
"@smithy/smithy-client": "^4.12.9",
"@smithy/types": "^4.14.0",
"@smithy/util-stream": "^4.5.22",
"@aws-sdk/core": "^3.977.4",
"@aws-sdk/types": "^3.974.2",
"@smithy/core": "^3.31.1",
"@smithy/fetch-http-handler": "^5.6.13",
"@smithy/node-http-handler": "^4.9.13",
"@smithy/types": "^4.16.1",
"tslib": "^2.6.2"
},
"engines": {
@ -454,24 +475,23 @@
}
},
"node_modules/@aws-sdk/credential-provider-ini": {
"version": "3.972.29",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.29.tgz",
"integrity": "sha512-SiBuAnXecCbT/OpAf3vqyI/AVE3mTaYr9ShXLybxZiPLBiPCCOIWSGAtYYGQWMRvobBTiqOewaB+wcgMMZI2Aw==",
"version": "3.973.10",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.10.tgz",
"integrity": "sha512-Zh9XRaPnDN9buO7GfWBubS22R6Nq5D6hbyYEMN05LiOnXugm/8WDjUx6y756bSPbdn3aJB2qG4zFW3bN82QhoQ==",
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "^3.973.27",
"@aws-sdk/credential-provider-env": "^3.972.25",
"@aws-sdk/credential-provider-http": "^3.972.27",
"@aws-sdk/credential-provider-login": "^3.972.29",
"@aws-sdk/credential-provider-process": "^3.972.25",
"@aws-sdk/credential-provider-sso": "^3.972.29",
"@aws-sdk/credential-provider-web-identity": "^3.972.29",
"@aws-sdk/nested-clients": "^3.996.19",
"@aws-sdk/types": "^3.973.7",
"@smithy/credential-provider-imds": "^4.2.13",
"@smithy/property-provider": "^4.2.13",
"@smithy/shared-ini-file-loader": "^4.4.8",
"@smithy/types": "^4.14.0",
"@aws-sdk/core": "^3.977.4",
"@aws-sdk/credential-provider-env": "^3.972.65",
"@aws-sdk/credential-provider-http": "^3.972.67",
"@aws-sdk/credential-provider-login": "^3.972.72",
"@aws-sdk/credential-provider-process": "^3.972.65",
"@aws-sdk/credential-provider-sso": "^3.973.9",
"@aws-sdk/credential-provider-web-identity": "^3.972.71",
"@aws-sdk/nested-clients": "^3.997.39",
"@aws-sdk/types": "^3.974.2",
"@smithy/core": "^3.31.1",
"@smithy/credential-provider-imds": "^4.4.16",
"@smithy/types": "^4.16.1",
"tslib": "^2.6.2"
},
"engines": {
@ -479,18 +499,16 @@
}
},
"node_modules/@aws-sdk/credential-provider-login": {
"version": "3.972.29",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.29.tgz",
"integrity": "sha512-OGOslTbOlxXexKMqhxCEbBQbUIfuhGxU5UXw3Fm56ypXHvrXH4aTt/xb5Y884LOoteP1QST1lVZzHfcTnWhiPQ==",
"version": "3.972.72",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.72.tgz",
"integrity": "sha512-zZapIKwaHp7TdTf9hbH1I3CVUdEupmt7FXO/BoTQGC+4h6NkXKWpqF2p5WyfpjurDLHCpSyh+BzMlAg8arqWLA==",
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "^3.973.27",
"@aws-sdk/nested-clients": "^3.996.19",
"@aws-sdk/types": "^3.973.7",
"@smithy/property-provider": "^4.2.13",
"@smithy/protocol-http": "^5.3.13",
"@smithy/shared-ini-file-loader": "^4.4.8",
"@smithy/types": "^4.14.0",
"@aws-sdk/core": "^3.977.4",
"@aws-sdk/nested-clients": "^3.997.39",
"@aws-sdk/types": "^3.974.2",
"@smithy/core": "^3.31.1",
"@smithy/types": "^4.16.1",
"tslib": "^2.6.2"
},
"engines": {
@ -498,22 +516,21 @@
}
},
"node_modules/@aws-sdk/credential-provider-node": {
"version": "3.972.30",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.30.tgz",
"integrity": "sha512-FMnAnWxc8PG+ZrZ2OBKzY4luCUJhe9CG0B9YwYr4pzrYGLXBS2rl+UoUvjGbAwiptxRL6hyA3lFn03Bv1TLqTw==",
"version": "3.972.76",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.76.tgz",
"integrity": "sha512-1yzLmRiYSgGC25v7ZZEwJn/auhHHTIHgFOmzL2f36hf1+7jSLcX+1QrAz4760WEzPiiQl8xmlpFhHfl2OoyVzA==",
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/credential-provider-env": "^3.972.25",
"@aws-sdk/credential-provider-http": "^3.972.27",
"@aws-sdk/credential-provider-ini": "^3.972.29",
"@aws-sdk/credential-provider-process": "^3.972.25",
"@aws-sdk/credential-provider-sso": "^3.972.29",
"@aws-sdk/credential-provider-web-identity": "^3.972.29",
"@aws-sdk/types": "^3.973.7",
"@smithy/credential-provider-imds": "^4.2.13",
"@smithy/property-provider": "^4.2.13",
"@smithy/shared-ini-file-loader": "^4.4.8",
"@smithy/types": "^4.14.0",
"@aws-sdk/credential-provider-env": "^3.972.65",
"@aws-sdk/credential-provider-http": "^3.972.67",
"@aws-sdk/credential-provider-ini": "^3.973.10",
"@aws-sdk/credential-provider-process": "^3.972.65",
"@aws-sdk/credential-provider-sso": "^3.973.9",
"@aws-sdk/credential-provider-web-identity": "^3.972.71",
"@aws-sdk/types": "^3.974.2",
"@smithy/core": "^3.31.1",
"@smithy/credential-provider-imds": "^4.4.16",
"@smithy/types": "^4.16.1",
"tslib": "^2.6.2"
},
"engines": {
@ -521,16 +538,15 @@
}
},
"node_modules/@aws-sdk/credential-provider-process": {
"version": "3.972.25",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.25.tgz",
"integrity": "sha512-HR7ynNRdNhNsdVCOCegy1HsfsRzozCOPtD3RzzT1JouuaHobWyRfJzCBue/3jP7gECHt+kQyZUvwg/cYLWurNQ==",
"version": "3.972.65",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.65.tgz",
"integrity": "sha512-e5DbbNteOSalN58U83G6kFa4ECLEuGbGqNBHIXE7zYXA/m4GHblIGjFbSH7wYv6gBV8iNSDcRZBKfQZF5vF9nw==",
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "^3.973.27",
"@aws-sdk/types": "^3.973.7",
"@smithy/property-provider": "^4.2.13",
"@smithy/shared-ini-file-loader": "^4.4.8",
"@smithy/types": "^4.14.0",
"@aws-sdk/core": "^3.977.4",
"@aws-sdk/types": "^3.974.2",
"@smithy/core": "^3.31.1",
"@smithy/types": "^4.16.1",
"tslib": "^2.6.2"
},
"engines": {
@ -538,18 +554,17 @@
}
},
"node_modules/@aws-sdk/credential-provider-sso": {
"version": "3.972.29",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.29.tgz",
"integrity": "sha512-HWv4SEq3jZDYPlwryZVef97+U8CxxRos5mK8sgGO1dQaFZpV5giZLzqGE5hkDmh2csYcBO2uf5XHjPTpZcJlig==",
"version": "3.973.9",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.9.tgz",
"integrity": "sha512-0V0u4t+KBku9fbh5CPCaC5hUWwSzDafp8nCuDy817zWbp2gz80jO44rMQkiwnZ+k54B+tjAtzRy00DJRGTKGBg==",
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "^3.973.27",
"@aws-sdk/nested-clients": "^3.996.19",
"@aws-sdk/token-providers": "3.1026.0",
"@aws-sdk/types": "^3.973.7",
"@smithy/property-provider": "^4.2.13",
"@smithy/shared-ini-file-loader": "^4.4.8",
"@smithy/types": "^4.14.0",
"@aws-sdk/core": "^3.977.4",
"@aws-sdk/nested-clients": "^3.997.39",
"@aws-sdk/token-providers": "3.1100.0",
"@aws-sdk/types": "^3.974.2",
"@smithy/core": "^3.31.1",
"@smithy/types": "^4.16.1",
"tslib": "^2.6.2"
},
"engines": {
@ -557,17 +572,16 @@
}
},
"node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": {
"version": "3.1026.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1026.0.tgz",
"integrity": "sha512-Ieq/HiRrbEtrYP387Nes0XlR7H1pJiJOZKv+QyQzMYpvTiDs0VKy2ZB3E2Zf+aFovWmeE7lRE4lXyF7dYM6GgA==",
"version": "3.1100.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1100.0.tgz",
"integrity": "sha512-THf3MkgY3fNJZ3zdgSenLqR7gSE68KccCj1RCKretlG73Ppszvues02VpCUO9NlB/tZDC483FvGCld+AiPCkvg==",
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "^3.973.27",
"@aws-sdk/nested-clients": "^3.996.19",
"@aws-sdk/types": "^3.973.7",
"@smithy/property-provider": "^4.2.13",
"@smithy/shared-ini-file-loader": "^4.4.8",
"@smithy/types": "^4.14.0",
"@aws-sdk/core": "^3.977.4",
"@aws-sdk/nested-clients": "^3.997.39",
"@aws-sdk/types": "^3.974.2",
"@smithy/core": "^3.31.1",
"@smithy/types": "^4.16.1",
"tslib": "^2.6.2"
},
"engines": {
@ -575,17 +589,16 @@
}
},
"node_modules/@aws-sdk/credential-provider-web-identity": {
"version": "3.972.29",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.29.tgz",
"integrity": "sha512-PdMBza1WEKEUPFEmMGCfnU2RYCz9MskU2e8JxjyUOsMKku7j9YaDKvbDi2dzC0ihFoM6ods2SbhfAAro+Gwlew==",
"version": "3.972.71",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.71.tgz",
"integrity": "sha512-e4dwiRltGAaQ+2yxw57Hj0l/BF3BHiG14+QpYE7bGYBlpAq/fkIri2BDhjWon8c0mhhtd2txQBAkQb9BcTStFg==",
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "^3.973.27",
"@aws-sdk/nested-clients": "^3.996.19",
"@aws-sdk/types": "^3.973.7",
"@smithy/property-provider": "^4.2.13",
"@smithy/shared-ini-file-loader": "^4.4.8",
"@smithy/types": "^4.14.0",
"@aws-sdk/core": "^3.977.4",
"@aws-sdk/nested-clients": "^3.997.39",
"@aws-sdk/types": "^3.974.2",
"@smithy/core": "^3.31.1",
"@smithy/types": "^4.16.1",
"tslib": "^2.6.2"
},
"engines": {
@ -842,48 +855,18 @@
}
},
"node_modules/@aws-sdk/nested-clients": {
"version": "3.996.19",
"resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.19.tgz",
"integrity": "sha512-uFkmCDXvmQYLanlYdOFS0+MQWkrj9wPMt/ZCc/0J0fjPim6F5jBVBmEomvGY/j77ILW6GTPwN22Jc174Mhkw6Q==",
"version": "3.997.39",
"resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.39.tgz",
"integrity": "sha512-wU5NPnj62Sb7A8xn/Zb+xThe05P3otNtDl37iOIi5DDMeCesNeCckaG+eXWGUs12Z9R34I8CD05TaTe6SIa61g==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
"@aws-sdk/core": "^3.973.27",
"@aws-sdk/middleware-host-header": "^3.972.9",
"@aws-sdk/middleware-logger": "^3.972.9",
"@aws-sdk/middleware-recursion-detection": "^3.972.10",
"@aws-sdk/middleware-user-agent": "^3.972.29",
"@aws-sdk/region-config-resolver": "^3.972.11",
"@aws-sdk/types": "^3.973.7",
"@aws-sdk/util-endpoints": "^3.996.6",
"@aws-sdk/util-user-agent-browser": "^3.972.9",
"@aws-sdk/util-user-agent-node": "^3.973.15",
"@smithy/config-resolver": "^4.4.14",
"@smithy/core": "^3.23.14",
"@smithy/fetch-http-handler": "^5.3.16",
"@smithy/hash-node": "^4.2.13",
"@smithy/invalid-dependency": "^4.2.13",
"@smithy/middleware-content-length": "^4.2.13",
"@smithy/middleware-endpoint": "^4.4.29",
"@smithy/middleware-retry": "^4.5.0",
"@smithy/middleware-serde": "^4.2.17",
"@smithy/middleware-stack": "^4.2.13",
"@smithy/node-config-provider": "^4.3.13",
"@smithy/node-http-handler": "^4.5.2",
"@smithy/protocol-http": "^5.3.13",
"@smithy/smithy-client": "^4.12.9",
"@smithy/types": "^4.14.0",
"@smithy/url-parser": "^4.2.13",
"@smithy/util-base64": "^4.3.2",
"@smithy/util-body-length-browser": "^4.2.2",
"@smithy/util-body-length-node": "^4.2.3",
"@smithy/util-defaults-mode-browser": "^4.3.45",
"@smithy/util-defaults-mode-node": "^4.2.49",
"@smithy/util-endpoints": "^3.3.4",
"@smithy/util-middleware": "^4.2.13",
"@smithy/util-retry": "^4.3.0",
"@smithy/util-utf8": "^4.2.2",
"@aws-sdk/core": "^3.977.4",
"@aws-sdk/signature-v4-multi-region": "^3.996.43",
"@aws-sdk/types": "^3.974.2",
"@smithy/core": "^3.31.1",
"@smithy/fetch-http-handler": "^5.6.13",
"@smithy/node-http-handler": "^4.9.13",
"@smithy/types": "^4.16.1",
"tslib": "^2.6.2"
},
"engines": {
@ -907,16 +890,14 @@
}
},
"node_modules/@aws-sdk/signature-v4-multi-region": {
"version": "3.996.9",
"resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.9.tgz",
"integrity": "sha512-2aAUwudVQ3uNkCfkBLQwNVD2jkfb299NSeDueXsT2NcNdFrWtHRkiQzX3wk47UFYbm87BkdxrsAJcQO7PdQOhA==",
"version": "3.996.43",
"resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.43.tgz",
"integrity": "sha512-lKekx8bLBXSv4O+cslk9Zfnw2XKSkWBs3uWL5QGhH2ZAQfNS7FE0vcSSN2vD/AhxX54ZTywWxR4STThoeOXlBA==",
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/middleware-sdk-s3": "^3.972.21",
"@aws-sdk/types": "^3.973.6",
"@smithy/protocol-http": "^5.3.12",
"@smithy/signature-v4": "^5.3.12",
"@smithy/types": "^4.13.1",
"@aws-sdk/types": "^3.974.2",
"@smithy/signature-v4": "^5.6.12",
"@smithy/types": "^4.16.1",
"tslib": "^2.6.2"
},
"engines": {
@ -942,12 +923,12 @@
}
},
"node_modules/@aws-sdk/types": {
"version": "3.973.7",
"resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.7.tgz",
"integrity": "sha512-reXRwoJ6CfChoqAsBszUYajAF8Z2LRE+CRcKocvFSMpIiLOtYU3aJ9trmn6VVPAzbbY5LXF+FfmUslbXk1SYFg==",
"version": "3.974.2",
"resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.2.tgz",
"integrity": "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/types": "^4.14.0",
"@smithy/types": "^4.16.1",
"tslib": "^2.6.2"
},
"engines": {
@ -1047,13 +1028,12 @@
}
},
"node_modules/@aws-sdk/xml-builder": {
"version": "3.972.17",
"resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.17.tgz",
"integrity": "sha512-Ra7hjqAZf1OXRRMueB13qex7mFJRDK/pgCvdSFemXBT8KCGnQDPoKzHY1SjN+TjJVmnpSF14W5tJ1vDamFu+Gg==",
"version": "3.972.37",
"resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.37.tgz",
"integrity": "sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/types": "^4.14.0",
"fast-xml-parser": "5.5.8",
"@smithy/types": "^4.16.1",
"tslib": "^2.6.2"
},
"engines": {
@ -3694,20 +3674,12 @@
}
},
"node_modules/@smithy/core": {
"version": "3.23.14",
"resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.14.tgz",
"integrity": "sha512-vJ0IhpZxZAkFYOegMKSrxw7ujhhT2pass/1UEcZ4kfl5srTAqtPU5I7MdYQoreVas3204ykCiNhY1o7Xlz6Yyg==",
"version": "3.31.1",
"resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.31.1.tgz",
"integrity": "sha512-CyogUINxvi7C7LDsh8Syo6hVJOT9ckz4rG8dRZfTJ8r91HkMY59PnNooaj7WcHyxEkxPfBAmbgztZU+xTo76lg==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/protocol-http": "^5.3.13",
"@smithy/types": "^4.14.0",
"@smithy/url-parser": "^4.2.13",
"@smithy/util-base64": "^4.3.2",
"@smithy/util-body-length-browser": "^4.2.2",
"@smithy/util-middleware": "^4.2.13",
"@smithy/util-stream": "^4.5.22",
"@smithy/util-utf8": "^4.2.2",
"@smithy/uuid": "^1.1.2",
"@smithy/types": "^4.16.1",
"tslib": "^2.6.2"
},
"engines": {
@ -3715,15 +3687,13 @@
}
},
"node_modules/@smithy/credential-provider-imds": {
"version": "4.2.13",
"resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.13.tgz",
"integrity": "sha512-wboCPijzf6RJKLOvnjDAiBxGSmSnGXj35o5ZAWKDaHa/cvQ5U3ZJ13D4tMCE8JG4dxVAZFy/P0x/V9CwwdfULQ==",
"version": "4.4.16",
"resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.16.tgz",
"integrity": "sha512-QfuLWAkLzptffFW980AFeHZFdqds2B64rpEd3uJ6lgs3xVn9QegGMUgUcj+4d7dRrAsya3r58ZKpku97WcFb4w==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/node-config-provider": "^4.3.13",
"@smithy/property-provider": "^4.2.13",
"@smithy/types": "^4.14.0",
"@smithy/url-parser": "^4.2.13",
"@smithy/core": "^3.31.1",
"@smithy/types": "^4.16.1",
"tslib": "^2.6.2"
},
"engines": {
@ -3801,15 +3771,13 @@
}
},
"node_modules/@smithy/fetch-http-handler": {
"version": "5.3.16",
"resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.16.tgz",
"integrity": "sha512-nYDRUIvNd4mFmuXraRWt6w5UsZTNqtj4hXJA/iiOD4tuseIdLP9Lq38teH/SZTcIFCa2f+27o7hYpIsWktJKEQ==",
"version": "5.6.13",
"resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.6.13.tgz",
"integrity": "sha512-4fW86pEUOMbrD5nkbyl/tTvPHHWJFbuB2odl6ps9lWfHoXf9HWh3Q/Smh59qH1g7+c/BSZghX6bbUk4gsiMs8A==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/protocol-http": "^5.3.13",
"@smithy/querystring-builder": "^4.2.13",
"@smithy/types": "^4.14.0",
"@smithy/util-base64": "^4.3.2",
"@smithy/core": "^3.31.1",
"@smithy/types": "^4.16.1",
"tslib": "^2.6.2"
},
"engines": {
@ -3997,14 +3965,13 @@
}
},
"node_modules/@smithy/node-http-handler": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.2.tgz",
"integrity": "sha512-/oD7u8M0oj2ZTFw7GkuuHWpIxtWdLlnyNkbrWcyVYhd5RJNDuczdkb0wfnQICyNFrVPlr8YHOhamjNy3zidhmA==",
"version": "4.9.13",
"resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.13.tgz",
"integrity": "sha512-Nmd/Nl35zfYrd+a6OO2cDJb3GPh9bgTjIUhcM+JFfjpp8/osCgboDV5nCT1I01Pv6R13eSKDKLSoVa5ZB6Zsfw==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/protocol-http": "^5.3.13",
"@smithy/querystring-builder": "^4.2.13",
"@smithy/types": "^4.14.0",
"@smithy/core": "^3.31.1",
"@smithy/types": "^4.16.1",
"tslib": "^2.6.2"
},
"engines": {
@ -4090,18 +4057,13 @@
}
},
"node_modules/@smithy/signature-v4": {
"version": "5.3.13",
"resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.13.tgz",
"integrity": "sha512-YpYSyM0vMDwKbHD/JA7bVOF6kToVRpa+FM5ateEVRpsTNu564g1muBlkTubXhSKKYXInhpADF46FPyrZcTLpXg==",
"version": "5.6.12",
"resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.6.12.tgz",
"integrity": "sha512-I6KLtq3H0qqSuV9vLglfi8puHqzygzWHOnI4z/Rdoo+q50vvo18vBRdPAvvEtcaKROz7Zn6qnPa14kRfPH6PcQ==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/is-array-buffer": "^4.2.2",
"@smithy/protocol-http": "^5.3.13",
"@smithy/types": "^4.14.0",
"@smithy/util-hex-encoding": "^4.2.2",
"@smithy/util-middleware": "^4.2.13",
"@smithy/util-uri-escape": "^4.2.2",
"@smithy/util-utf8": "^4.2.2",
"@smithy/core": "^3.31.1",
"@smithy/types": "^4.16.1",
"tslib": "^2.6.2"
},
"engines": {
@ -4127,9 +4089,9 @@
}
},
"node_modules/@smithy/types": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.0.tgz",
"integrity": "sha512-OWgntFLW88kx2qvf/c/67Vno1yuXm/f9M7QFAtVkkO29IJXGBIg0ycEaBTH0kvCtwmvZxRujrgP5a86RvsXJAQ==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.16.1.tgz",
"integrity": "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@ -6617,6 +6579,7 @@
}
],
"license": "MIT",
"optional": true,
"dependencies": {
"path-expression-matcher": "^1.1.3"
}
@ -6632,6 +6595,7 @@
}
],
"license": "MIT",
"optional": true,
"dependencies": {
"fast-xml-builder": "^1.1.4",
"path-expression-matcher": "^1.2.0",
@ -9675,6 +9639,7 @@
}
],
"license": "MIT",
"optional": true,
"engines": {
"node": ">=14.0.0"
}
@ -10907,7 +10872,8 @@
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/strtok3": {
"version": "6.3.0",

View File

@ -34,6 +34,7 @@
},
"dependencies": {
"@aws-sdk/client-bedrock-runtime": "^3.1030.0",
"@aws-sdk/client-rekognition": "^3.1101.0",
"@aws-sdk/client-s3": "^3.1012.0",
"@aws-sdk/lib-storage": "^3.1012.0",
"@hapi/hapi": "^21.3.0",

View File

@ -412,10 +412,10 @@ const app = {
},
{ timezone: process.env.CRON_TZ || undefined },
);
// Automatic photo retention cleanup — runs daily at 02:00.
// Automatic photo retention cleanup — runs daily at ___.
const { runPhotoCleanup } = require('./services/photoCleanupService');
cron.schedule(
'0 2 * * *',
'45 9 * * *',
async () => {
try {
const r = await runPhotoCleanup();

View File

@ -144,6 +144,109 @@ async function runRekognitionIndexing(photo) {
}
}
async function getEnabledEventOrThrow(eventId, h) {
const event = await Event.findById(eventId).select('settings organization').lean();
if (!event) {
return { errorResponse: h.response(error('NOT_FOUND', 'Event not found')).code(404) };
}
const enabled = Boolean(event.settings?.pictureWallEnabled ?? false);
if (!enabled) {
return { errorResponse: h.response(error('DISABLED', 'Picture wall is not enabled for this event')).code(403) };
}
return { event };
}
const listPublicEventPhotos = async (request, h) => {
const { eventId } = request.params;
const result = await getEnabledEventOrThrow(eventId, h);
if (result.errorResponse) {
return result.errorResponse;
}
const photos = await EventPhoto.find({ event: eventId })
.populate('uploadedBy', 'firstName lastName')
.sort({ createdAt: -1 })
.lean();
const out = photos.map((p) => ({
_id: String(p._id),
event: String(p.event),
organization: String(p.organization),
uploadedBy: p.uploadedBy
? (p.uploadedBy.firstName || p.uploadedBy.lastName
? `${p.uploadedBy.firstName || ''} ${p.uploadedBy.lastName || ''}`.trim()
: String(p.uploadedBy._id || p.uploadedBy))
: null,
fileName: p.fileName,
originalFileName: p.originalFileName,
url: p.url,
s3Key: p.s3Key,
mimeType: p.mimeType,
size: p.size,
embedding_generated: p.embedding_generated,
uploadedAt: p.createdAt,
}));
return h.response(success({ photos: out }));
};
const uploadPublicEventPhotos = async (request, h) => {
const { eventId } = request.params;
const result = await getEnabledEventOrThrow(eventId, h);
if (result.errorResponse) {
return result.errorResponse;
}
const event = result.event;
const payload = request.payload || {};
const files = await extractUploadFiles(payload);
if (files.length === 0) {
return h.response(error('VALIDATION_ERROR', 'No files received. Use multipart/form-data with a binary field named "files".')).code(400);
}
const allowedImageTypes = ['image/jpeg', 'image/png', 'image/jpg', 'image/gif', 'image/webp', 'image/bmp'];
const orgId = event.organization || event?.organization?._id || null;
const uploadPromises = files.map(async (extracted) => {
const mime = extracted.mimeType || 'application/octet-stream';
if (mime !== 'application/octet-stream' && !allowedImageTypes.includes(mime)) {
return null;
}
const originalFileName = extracted.originalName;
const sanitizedOriginal = String(originalFileName || 'photo').replace(/[^a-zA-Z0-9._-]/g, '_');
const storedFilename = `${Date.now()}-${Math.random().toString(36).slice(2, 6)}-${sanitizedOriginal}`;
const uploaded = await uploadEventPhotoToS3({
buffer: extracted.buffer,
contentType: mime,
eventId: `picturewall/${eventId}`,
filename: storedFilename,
});
const photo = await EventPhoto.create({
event: eventId,
organization: orgId,
fileName: storedFilename,
originalFileName,
url: uploaded.url,
s3Key: uploaded.s3Key,
mimeType: mime,
size: extracted.buffer.length,
embedding_generated: false,
});
runRekognitionIndexing(photo);
return { ...photo.toObject(), _id: String(photo._id) };
});
const results = await Promise.all(uploadPromises);
const created = results.filter(Boolean);
return h.response(success({ photos: created }, `${created.length} photo(s) uploaded`, 201)).code(201);
};
const listEventPhotos = async (request, h) => {
const user = request.auth.credentials?.user;
const { eventId } = request.params;
@ -162,8 +265,6 @@ const listEventPhotos = async (request, h) => {
.sort({ createdAt: -1 })
.lean();
const out = photos.map((p) => ({
_id: String(p._id),
event: String(p.event),
@ -223,7 +324,7 @@ const uploadEventPhotos = async (request, h) => {
const uploaded = await uploadEventPhotoToS3({
buffer: extracted.buffer,
contentType: mime,
eventId,
eventId: `picturewall/${eventId}`,
filename: storedFilename,
});
@ -405,6 +506,263 @@ const searchEventPhotos = async (request, h) => {
}
};
const searchPublicEventPhotos = async (request, h) => {
const { eventId } = request.params;
// Validate event ID
if (!mongoose.Types.ObjectId.isValid(String(eventId))) {
return h
.response(error('VALIDATION_ERROR', 'Invalid event id'))
.code(400);
}
// Make sure the event exists and public picture wall is enabled
const result = await getEnabledEventOrThrow(eventId, h);
if (result.errorResponse) {
return result.errorResponse;
}
const files = await extractUploadFiles(request.payload || {});
if (files.length === 0) {
return h
.response(
error(
'VALIDATION_ERROR',
'No query image received. Use multipart/form-data with a binary field named "files".'
)
)
.code(400);
}
const query = files[0];
const mime = query.mimeType || 'application/octet-stream';
const allowedImageTypes = [
'image/jpeg',
'image/png',
'image/jpg',
'image/gif',
'image/webp',
'image/bmp',
];
if (
mime !== 'application/octet-stream' &&
!allowedImageTypes.includes(mime)
) {
return h
.response(
error('VALIDATION_ERROR', 'Unsupported query image type')
)
.code(400);
}
const tempFilename =
`search-${Date.now()}-${Math.random()
.toString(36)
.slice(2, 8)}`;
let tempS3Key = null;
try {
// Upload query image temporarily
const uploaded = await uploadEventPhotoToS3({
buffer: query.buffer,
contentType: mime,
eventId: `picturewall/search-tmp/${eventId}`,
filename: tempFilename,
});
tempS3Key = uploaded.s3Key;
// Search Rekognition collection
const { collectionExists, matches } =
await rekognitionService.searchPhoto(eventId, {
s3Key: tempS3Key,
bucket: undefined,
faceMatchThreshold: 50,
maxFaces: 100,
});
if (!collectionExists) {
return h
.response(
success(
{
collectionExists: false,
matchCount: 0,
matches: [],
},
'No collection exists for this event yet'
)
)
.code(200);
}
if (!matches || matches.length === 0) {
return h
.response(
success(
{
collectionExists: true,
matchCount: 0,
matches: [],
},
'No matches found'
)
)
.code(200);
}
const faceIds = matches
.map((m) => m.faceId)
.filter(Boolean);
const photos = await EventPhoto.find({
event: eventId,
'faces.faceId': { $in: faceIds },
}).lean();
// faceId -> photo
const photoByFaceId = new Map();
for (const photo of photos) {
for (const face of photo.faces || []) {
if (face && face.faceId) {
photoByFaceId.set(face.faceId, photo);
}
}
}
// Keep highest similarity per photo
const bestByPhoto = new Map();
for (const match of matches) {
const photo = photoByFaceId.get(match.faceId);
if (!photo) continue;
const existing = bestByPhoto.get(String(photo._id));
if (
!existing ||
match.similarity > existing.similarity
) {
bestByPhoto.set(String(photo._id), {
photo,
similarity: match.similarity,
matchedFace: match,
});
}
}
const results = Array.from(
bestByPhoto.values()
)
.map(
({
photo,
similarity,
matchedFace,
}) => ({
_id: String(photo._id),
event: String(photo.event),
organization: String(photo.organization),
uploadedBy: photo.uploadedBy
? String(photo.uploadedBy)
: null,
fileName: photo.fileName,
originalFileName: photo.originalFileName,
url: photo.url,
s3Key: photo.s3Key,
mimeType: photo.mimeType,
size: photo.size,
embedding_generated:
photo.embedding_generated,
uploadedAt: photo.createdAt,
similarity,
matchedFace: {
faceId: matchedFace.faceId,
similarity: matchedFace.similarity,
boundingBox: matchedFace.boundingBox,
},
})
)
.sort(
(a, b) =>
b.similarity - a.similarity
);
return h
.response(
success(
{
collectionExists: true,
matchCount: results.length,
matches: results,
},
`${results.length} photo(s) matched`
)
)
.code(200);
} catch (err) {
console.error(
'[eventPhoto] Public photo search failed:',
err?.name,
err?.message
);
if (
err &&
(
err.name === 'ResourceNotFoundException' ||
err.code === 'ResourceNotFoundException'
)
) {
return h
.response(
success(
{
collectionExists: false,
matchCount: 0,
matches: [],
},
'No collection exists for this event yet'
)
)
.code(200);
}
return h
.response(
error(
'REKOGNITION_ERROR',
'Photo search failed. Please try again later.'
)
)
.code(500);
} finally {
// Always delete temporary query image
if (tempS3Key) {
try {
await deleteEventPhotoFromS3({
s3Key: tempS3Key,
});
} catch (cleanupErr) {
console.error(
`[eventPhoto] Failed to delete temp query image ${tempS3Key}:`,
cleanupErr?.message
);
}
}
}
};
const deleteEventPhoto = async (request, h) => {
const user = request.auth.credentials?.user;
const { eventId, photoId } = request.params;
@ -441,9 +799,12 @@ const deleteEventPhoto = async (request, h) => {
};
module.exports = {
listPublicEventPhotos,
uploadPublicEventPhotos,
listEventPhotos,
uploadEventPhotos,
searchEventPhotos,
searchPublicEventPhotos,
deleteEventPhoto,
};

View File

@ -57,6 +57,7 @@ const eventSchema = new mongoose.Schema(
enableFeedback: { type: Boolean, default: true },
enableRatings: { type: Boolean, default: false },
webCheckInEnabled: { type: Boolean, default: false },
pictureWallEnabled: { type: Boolean, default: false },
/** otp_email | link_email | none */
authenticationType: String,
/** name_email | email_only | name_email_phone | anonymous_name | anonymous */

View File

@ -67,6 +67,7 @@ const baseEventPayload = {
enableFeedback: Joi.boolean(),
enableRatings: Joi.boolean(),
webCheckInEnabled: Joi.boolean(),
pictureWallEnabled: Joi.boolean(),
authenticationType: Joi.string().valid(...AUTHENTICATION_TYPES).allow('', null),
registrationBaseFields: Joi.string().valid(...REGISTRATION_BASE_FIELDS).allow('', null),
inviteCode: Joi.string().allow('', null),

View File

@ -11,6 +11,32 @@ const uploadPayload = Joi.object({
}).required();
module.exports = [
{
method: 'GET',
path: '/api/events/{eventId}/picture-wall',
options: {
auth: false,
handler: controller.listPublicEventPhotos,
},
},
{
method: 'POST',
path: '/api/events/{eventId}/picture-wall',
options: {
auth: false,
validate: {
payload: uploadPayload,
},
payload: {
maxBytes: 100 * 1024 * 1024,
output: 'stream',
parse: true,
multipart: true,
allow: 'multipart/form-data',
},
handler: controller.uploadPublicEventPhotos,
},
},
{
method: 'GET',
path: '/api/events/{eventId}/photos',
@ -55,6 +81,27 @@ module.exports = [
handler: controller.searchEventPhotos,
},
},
{
method: 'POST',
path: '/api/events/{eventId}/search',
options: {
auth: false,
validate: {
payload: searchPayload,
},
payload: {
maxBytes: 10 * 1024 * 1024,
output: 'stream',
parse: true,
multipart: true,
allow: 'multipart/form-data',
},
handler: controller.searchPublicEventPhotos,
},
},
{
method: 'DELETE',
path: '/api/events/{eventId}/photos/{photoId}',