Add event photo search backend
This commit is contained in:
parent
8cc36b2674
commit
49f9357f72
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
@ -54,6 +80,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',
|
||||
|
|
|
|||
Loading…
Reference in New Issue