sotre profile avatars in R2..
This commit is contained in:
Generated
+396
@@ -9,6 +9,7 @@
|
||||
"version": "0.1.0",
|
||||
"license": "UNLICENSED",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.1081.0",
|
||||
"@nestjs/common": "^11.1.24",
|
||||
"@nestjs/config": "^4.0.4",
|
||||
"@nestjs/core": "^11.1.24",
|
||||
@@ -168,6 +169,314 @@
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/checksums": {
|
||||
"version": "3.1000.14",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1000.14.tgz",
|
||||
"integrity": "sha512-9j0TSNmYVywV+shhBXXkIhaazZwZ+nDSGfG6V47nFLqXdk6mPYk/OJkV4+uRvIg2Ju5F6HTvgM/sfsuTsbAYwg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.974.29",
|
||||
"@aws-sdk/types": "^3.973.15",
|
||||
"@smithy/core": "^3.29.0",
|
||||
"@smithy/types": "^4.15.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-s3": {
|
||||
"version": "3.1081.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1081.0.tgz",
|
||||
"integrity": "sha512-mHAdLBibsbFwcW2YHrH5sIQlaXjB3zha7Nkooa2gqTlXxRpVJ+ba/v/mg19B6rJeZmOEteRHFz+INOlAbCbxJg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/checksums": "^3.1000.14",
|
||||
"@aws-sdk/core": "^3.974.29",
|
||||
"@aws-sdk/credential-provider-node": "^3.972.64",
|
||||
"@aws-sdk/middleware-sdk-s3": "^3.972.60",
|
||||
"@aws-sdk/signature-v4-multi-region": "^3.996.38",
|
||||
"@aws-sdk/types": "^3.973.15",
|
||||
"@smithy/core": "^3.29.0",
|
||||
"@smithy/fetch-http-handler": "^5.6.2",
|
||||
"@smithy/node-http-handler": "^4.9.2",
|
||||
"@smithy/types": "^4.15.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/core": {
|
||||
"version": "3.974.29",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.29.tgz",
|
||||
"integrity": "sha512-yqKcltLbtRh1ubzhRSldIs8jFHNZlyMlgoIccCC0aDVbrB99nXaBdmfr89mK7obWX/NVg4rAMpCpZ6dCDiVBtA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/types": "^3.973.15",
|
||||
"@aws-sdk/xml-builder": "^3.972.33",
|
||||
"@aws/lambda-invoke-store": "^0.3.0",
|
||||
"@smithy/core": "^3.29.0",
|
||||
"@smithy/signature-v4": "^5.6.1",
|
||||
"@smithy/types": "^4.15.1",
|
||||
"bowser": "^2.11.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-env": {
|
||||
"version": "3.972.55",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.55.tgz",
|
||||
"integrity": "sha512-Ah36tYkqyaVnaHkx7VseoTYrHUmwgBps3V+wnrC1idhIIMGlviH0FtrX9EIPdAlVHvXC7FQZLhmHBRz+pLaiWg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.974.29",
|
||||
"@aws-sdk/types": "^3.973.15",
|
||||
"@smithy/core": "^3.29.0",
|
||||
"@smithy/types": "^4.15.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-http": {
|
||||
"version": "3.972.57",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.57.tgz",
|
||||
"integrity": "sha512-/vp6i5YEliJqRm5k/BDmYjAyRAMTdkjW6UciVRk9oh/0OfDCWeb/ih7hqte4lFvKXkIbsqe9AdK9LQK6NGardw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.974.29",
|
||||
"@aws-sdk/types": "^3.973.15",
|
||||
"@smithy/core": "^3.29.0",
|
||||
"@smithy/fetch-http-handler": "^5.6.2",
|
||||
"@smithy/node-http-handler": "^4.9.2",
|
||||
"@smithy/types": "^4.15.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-ini": {
|
||||
"version": "3.972.62",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.62.tgz",
|
||||
"integrity": "sha512-pQIRiQQs+MUlVnJdWJ7/6KS0WxcLRVfut57OFgwC3cnM1F8mXw3Kh4gAVwj6AtvD6CWx8x6+po4ENRcqe64XrQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.974.29",
|
||||
"@aws-sdk/credential-provider-env": "^3.972.55",
|
||||
"@aws-sdk/credential-provider-http": "^3.972.57",
|
||||
"@aws-sdk/credential-provider-login": "^3.972.61",
|
||||
"@aws-sdk/credential-provider-process": "^3.972.55",
|
||||
"@aws-sdk/credential-provider-sso": "^3.972.61",
|
||||
"@aws-sdk/credential-provider-web-identity": "^3.972.61",
|
||||
"@aws-sdk/nested-clients": "^3.997.29",
|
||||
"@aws-sdk/types": "^3.973.15",
|
||||
"@smithy/core": "^3.29.0",
|
||||
"@smithy/credential-provider-imds": "^4.4.5",
|
||||
"@smithy/types": "^4.15.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-login": {
|
||||
"version": "3.972.61",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.61.tgz",
|
||||
"integrity": "sha512-jtrxWwC7slqxh7DnAWHrwsA3UwCsnlypdYtavGT7EX5p791wxWQys7QzkCZ7JvOMAyylDtPoxyV+ic0zg3rV9g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.974.29",
|
||||
"@aws-sdk/nested-clients": "^3.997.29",
|
||||
"@aws-sdk/types": "^3.973.15",
|
||||
"@smithy/core": "^3.29.0",
|
||||
"@smithy/types": "^4.15.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-node": {
|
||||
"version": "3.972.64",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.64.tgz",
|
||||
"integrity": "sha512-zyKVYDyMR9VQL/kPi03ygN2vtD9uLMuWRLoJ77KxgZZaS1VlJloI+SzleF9Zg4HWUI+AIu+ZRs8zsJFNqbrxsw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/credential-provider-env": "^3.972.55",
|
||||
"@aws-sdk/credential-provider-http": "^3.972.57",
|
||||
"@aws-sdk/credential-provider-ini": "^3.972.62",
|
||||
"@aws-sdk/credential-provider-process": "^3.972.55",
|
||||
"@aws-sdk/credential-provider-sso": "^3.972.61",
|
||||
"@aws-sdk/credential-provider-web-identity": "^3.972.61",
|
||||
"@aws-sdk/types": "^3.973.15",
|
||||
"@smithy/core": "^3.29.0",
|
||||
"@smithy/credential-provider-imds": "^4.4.5",
|
||||
"@smithy/types": "^4.15.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-process": {
|
||||
"version": "3.972.55",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.55.tgz",
|
||||
"integrity": "sha512-x0XjjF0l1WGRtK2vEhTZqCguQuAIZLep9l2+eeEmuxQQjjD3BlGQXY5xADR+l3t576UX+dxRkRtTjEu40l81Vw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.974.29",
|
||||
"@aws-sdk/types": "^3.973.15",
|
||||
"@smithy/core": "^3.29.0",
|
||||
"@smithy/types": "^4.15.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-sso": {
|
||||
"version": "3.972.61",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.61.tgz",
|
||||
"integrity": "sha512-d/V0VRsz73i+PHhbult/tx0Y1+de1SNQVsXkcQCmpfeBq7uODy/RTxNsOLpT9ZVHxcRNzbQFuywLKC33fUMIxA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.974.29",
|
||||
"@aws-sdk/nested-clients": "^3.997.29",
|
||||
"@aws-sdk/token-providers": "3.1081.0",
|
||||
"@aws-sdk/types": "^3.973.15",
|
||||
"@smithy/core": "^3.29.0",
|
||||
"@smithy/types": "^4.15.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-web-identity": {
|
||||
"version": "3.972.61",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.61.tgz",
|
||||
"integrity": "sha512-Bv4n3NOI6hPy+rmr6Bw9R6LnBVRkcp3ncj2E2IKSYJG+0UkysSitWMvbgndNvMxDw7gE1pQ/ErwkNceuKwj7zQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.974.29",
|
||||
"@aws-sdk/nested-clients": "^3.997.29",
|
||||
"@aws-sdk/types": "^3.973.15",
|
||||
"@smithy/core": "^3.29.0",
|
||||
"@smithy/types": "^4.15.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/middleware-sdk-s3": {
|
||||
"version": "3.972.60",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.60.tgz",
|
||||
"integrity": "sha512-f8SREKL6uQUYt8fwreiUgaAJQ31/M+UkWfJMDY/TBJC5FdHgNzxuvRFeZ9eMbnf2ydQ3p4pcKEhaLk35DDIPxw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.974.29",
|
||||
"@aws-sdk/signature-v4-multi-region": "^3.996.38",
|
||||
"@aws-sdk/types": "^3.973.15",
|
||||
"@smithy/core": "^3.29.0",
|
||||
"@smithy/types": "^4.15.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/nested-clients": {
|
||||
"version": "3.997.29",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.29.tgz",
|
||||
"integrity": "sha512-ot6v8J5W8P0w6ryyuIkXP1bHZHTlvwtn83mVCYaBE0GJ6tJX4vPSBx7M98w9O4wmmDruFsDBUMjhEHA+OosUFQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.974.29",
|
||||
"@aws-sdk/signature-v4-multi-region": "^3.996.38",
|
||||
"@aws-sdk/types": "^3.973.15",
|
||||
"@smithy/core": "^3.29.0",
|
||||
"@smithy/fetch-http-handler": "^5.6.2",
|
||||
"@smithy/node-http-handler": "^4.9.2",
|
||||
"@smithy/types": "^4.15.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/signature-v4-multi-region": {
|
||||
"version": "3.996.38",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.38.tgz",
|
||||
"integrity": "sha512-C379Sk+MiFZCfWZphKlMyLHKxV22OjoGM5KJjj5IJNJcOCWL4IGIpnEGzv1FQiRwhYXfq55SJMfxlqPE08JJ9g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/types": "^3.973.15",
|
||||
"@smithy/signature-v4": "^5.6.1",
|
||||
"@smithy/types": "^4.15.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/token-providers": {
|
||||
"version": "3.1081.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1081.0.tgz",
|
||||
"integrity": "sha512-kduAeI6cL+zqwj3gjPh9LhuX7kBZ83msYxutavaR+UPm5K8J7iThJBvNRAsFNyWTji92CSU8dogUgvi9T0BehA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.974.29",
|
||||
"@aws-sdk/nested-clients": "^3.997.29",
|
||||
"@aws-sdk/types": "^3.973.15",
|
||||
"@smithy/core": "^3.29.0",
|
||||
"@smithy/types": "^4.15.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/types": {
|
||||
"version": "3.973.15",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.15.tgz",
|
||||
"integrity": "sha512-IULn8uBV/SMtmOIANsm4WHXIOtVPBWfOWs3WGL0j/sI+KhaYehvOw0ET+9urnn8MBpiijuU/0JOpuwKOE451PQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/types": "^4.15.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/xml-builder": {
|
||||
"version": "3.972.33",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.33.tgz",
|
||||
"integrity": "sha512-ezbwz9WpuLctm6o7P2t2naDhVVPI5jFGrVefVybhcKGjU57VIyT46pQVO0RI2RYkUdhdj2Z9uSIlAzGZE9NW9A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/types": "^4.15.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"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/@babel/code-frame": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
|
||||
@@ -2605,6 +2914,87 @@
|
||||
"@sinonjs/commons": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/core": {
|
||||
"version": "3.29.1",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.29.1.tgz",
|
||||
"integrity": "sha512-qoiY4nrk5OCu1+eIR1VB8l5DmON/oKiqrd5zZFAhXJXjJlLWQusKEW/SkBDAtGDcPaz86m9kfcE1lngU0GlM6A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/types": "^4.15.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/credential-provider-imds": {
|
||||
"version": "4.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.6.tgz",
|
||||
"integrity": "sha512-B2WQ/PV/H6Jeg3lrIq6bKUfa6Hy01mtK7CGs6lhjzHA6k4aagldH6T6eEjnzKl4HI0cJnAsxfJ19pgb5PV+CVQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/core": "^3.29.1",
|
||||
"@smithy/types": "^4.15.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/fetch-http-handler": {
|
||||
"version": "5.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.6.3.tgz",
|
||||
"integrity": "sha512-CwCc/7SMTj45y97MUnDTbTaxvtAsiNNRm81z3abROIuMbMsC2Iy5EKfkkVdsKrz8WExQAAMx1EJapq+9j4fFTQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/core": "^3.29.1",
|
||||
"@smithy/types": "^4.15.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/node-http-handler": {
|
||||
"version": "4.9.3",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.3.tgz",
|
||||
"integrity": "sha512-qZTa4gQFUo8RM02rk6q5UVTDLNrQ1oS20LsepBzqq1QBVc/EHJ03OOUADcqMZiXHArW+Y7+OGY0BpdTwZRq/Yg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/core": "^3.29.1",
|
||||
"@smithy/types": "^4.15.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/signature-v4": {
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.6.2.tgz",
|
||||
"integrity": "sha512-QgHflghMoPxCJ9axiCVh8KZfbC9fuP6vkXXyK//E3cq7nLaSSyyLj0GAoqVWezYeDQmXIZhmlRvLE16jsqDK6g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/core": "^3.29.1",
|
||||
"@smithy/types": "^4.15.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/types": {
|
||||
"version": "4.15.1",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.15.1.tgz",
|
||||
"integrity": "sha512-x3L0XSACF6UYzKpa9biqiRMgvH5+wnFFew9Tm/grFYqgaupPwx/+ojDPpPJM8dZON3S9tjz5U+PQYsCBd1Mw5Q==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@socket.io/component-emitter": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
|
||||
@@ -4172,6 +4562,12 @@
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/bowser": {
|
||||
"version": "2.14.1",
|
||||
"resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz",
|
||||
"integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"test:cov": "jest --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.1081.0",
|
||||
"@nestjs/common": "^11.1.24",
|
||||
"@nestjs/config": "^4.0.4",
|
||||
"@nestjs/core": "^11.1.24",
|
||||
|
||||
@@ -30,6 +30,8 @@ export class CurriculumController {
|
||||
this.getBearerToken(request),
|
||||
referenceId,
|
||||
);
|
||||
if (download.url) return response.redirect(download.url);
|
||||
|
||||
response.setHeader('content-type', download.contentType);
|
||||
return response.download(download.path, download.fileName);
|
||||
}
|
||||
|
||||
@@ -3,12 +3,13 @@ import { Module } from '@nestjs/common';
|
||||
import { AuthModule } from '../auth/auth.module';
|
||||
import { PlatformControlModule } from '../platform-control/platform-control.module';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
import { R2PublicFileService } from '../storage/r2-public-file.service';
|
||||
import { CurriculumController } from './curriculum.controller';
|
||||
import { CurriculumService } from './curriculum.service';
|
||||
|
||||
@Module({
|
||||
imports: [AuthModule, PrismaModule, PlatformControlModule],
|
||||
controllers: [CurriculumController],
|
||||
providers: [CurriculumService],
|
||||
providers: [CurriculumService, R2PublicFileService],
|
||||
})
|
||||
export class CurriculumModule {}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { join } from 'node:path';
|
||||
import { AuthService } from '../auth/auth.service';
|
||||
import { PlatformControlService } from '../platform-control/platform-control.service';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { R2PublicFileService } from '../storage/r2-public-file.service';
|
||||
|
||||
const COURSE_REFERENCE_UPLOAD_ROOT = join(process.cwd(), 'uploads', 'course-references');
|
||||
const PUBLIC_GUIDE_COURSE_SLUG = 'autonomy-studio-lms-guide';
|
||||
@@ -15,6 +16,7 @@ export class CurriculumService {
|
||||
private readonly authService: AuthService,
|
||||
private readonly platformControlService: PlatformControlService,
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly r2PublicFileService: R2PublicFileService,
|
||||
) {}
|
||||
|
||||
async listCourses(accessToken?: string) {
|
||||
@@ -511,6 +513,16 @@ export class CurriculumService {
|
||||
throw new NotFoundException('Reference not found');
|
||||
}
|
||||
|
||||
const publicUrl = this.r2PublicFileService.publicUrl(reference.storedFileName);
|
||||
if (publicUrl) {
|
||||
return {
|
||||
url: publicUrl,
|
||||
path: '',
|
||||
fileName: reference.originalFileName,
|
||||
contentType: reference.contentType ?? 'application/octet-stream',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
path: join(COURSE_REFERENCE_UPLOAD_ROOT, reference.storedFileName),
|
||||
fileName: reference.originalFileName,
|
||||
@@ -851,6 +863,7 @@ export class CurriculumService {
|
||||
kind: string;
|
||||
title: string;
|
||||
originalFileName: string;
|
||||
storedFileName: string;
|
||||
contentType: string | null;
|
||||
fileSizeBytes: number;
|
||||
createdAt: Date;
|
||||
@@ -867,7 +880,7 @@ export class CurriculumService {
|
||||
fileSizeBytes: reference.fileSizeBytes,
|
||||
week,
|
||||
weekNumber: week,
|
||||
downloadUrl: `/curriculum/references/${reference.id}/download`,
|
||||
downloadUrl: this.r2PublicFileService.publicUrl(reference.storedFileName) ?? `/curriculum/references/${reference.id}/download`,
|
||||
createdAt: reference.createdAt.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,12 +4,13 @@ import { AuthModule } from '../auth/auth.module';
|
||||
import { ChatModule } from '../chat/chat.module';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
import { PlatformGatewayClient } from '../platform-control/platform-gateway.client';
|
||||
import { R2PublicFileService } from '../storage/r2-public-file.service';
|
||||
import { InstructorController } from './instructor.controller';
|
||||
import { InstructorService } from './instructor.service';
|
||||
|
||||
@Module({
|
||||
imports: [AuthModule, ChatModule, PrismaModule],
|
||||
controllers: [InstructorController],
|
||||
providers: [InstructorService, PlatformGatewayClient],
|
||||
providers: [InstructorService, PlatformGatewayClient, R2PublicFileService],
|
||||
})
|
||||
export class InstructorModule {}
|
||||
|
||||
@@ -17,6 +17,7 @@ import { AuthService, type AuthResponseUser } from '../auth/auth.service';
|
||||
import { ChatGateway } from '../chat/chat.gateway';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { PlatformGatewayClient, type GatewayAccessAssignment, type GatewayAgent } from '../platform-control/platform-gateway.client';
|
||||
import { R2PublicFileService } from '../storage/r2-public-file.service';
|
||||
import { CreateInstructorPlatformDto } from './dto/create-instructor-platform.dto';
|
||||
import { CreateInstructorStudentDto } from './dto/create-instructor-student.dto';
|
||||
import { SaveInstructorGroupDto } from './dto/save-instructor-group.dto';
|
||||
@@ -64,6 +65,7 @@ export class InstructorService {
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly platformGatewayClient: PlatformGatewayClient,
|
||||
private readonly r2PublicFileService: R2PublicFileService,
|
||||
) {}
|
||||
|
||||
async listCourses(accessToken: string | undefined) {
|
||||
@@ -1534,9 +1536,27 @@ export class InstructorService {
|
||||
const kind = input.kind ?? 'REFERENCE';
|
||||
|
||||
const originalFileName = this.normalizeUploadedFileName(file.originalname);
|
||||
const fileId = randomUUID();
|
||||
const extension = extname(originalFileName || '');
|
||||
let storedFileName = `${fileId}${extension}`;
|
||||
if (this.r2PublicFileService.isConfigured()) {
|
||||
storedFileName = this.r2PublicFileService.createLmsCourseFileKey({
|
||||
courseId: course.id,
|
||||
week: targetWeek.week,
|
||||
kind,
|
||||
fileId,
|
||||
extension,
|
||||
});
|
||||
await this.r2PublicFileService.uploadPublicFile({
|
||||
key: storedFileName,
|
||||
body: file.buffer,
|
||||
contentType: file.mimetype,
|
||||
fileName: originalFileName,
|
||||
});
|
||||
} else {
|
||||
await mkdir(COURSE_REFERENCE_UPLOAD_ROOT, { recursive: true });
|
||||
const storedFileName = `${randomUUID()}${extname(originalFileName || '')}`;
|
||||
await writeFile(join(COURSE_REFERENCE_UPLOAD_ROOT, storedFileName), file.buffer);
|
||||
}
|
||||
|
||||
const maxSortOrder = await this.prisma.courseWeekReference.aggregate({
|
||||
where: { weekId: targetWeek.id, kind },
|
||||
@@ -1581,7 +1601,11 @@ export class InstructorService {
|
||||
}
|
||||
|
||||
await this.prisma.courseWeekReference.delete({ where: { id: reference.id } });
|
||||
if (this.r2PublicFileService.isPublicKey(reference.storedFileName)) {
|
||||
await this.r2PublicFileService.deletePublicFile(reference.storedFileName).catch(() => undefined);
|
||||
} else {
|
||||
await unlink(join(COURSE_REFERENCE_UPLOAD_ROOT, reference.storedFileName)).catch(() => undefined);
|
||||
}
|
||||
|
||||
return this.getCurriculum(accessToken, null, reference.week.courseId);
|
||||
}
|
||||
@@ -2837,6 +2861,7 @@ export class InstructorService {
|
||||
kind: string;
|
||||
title: string;
|
||||
originalFileName: string;
|
||||
storedFileName: string;
|
||||
contentType: string | null;
|
||||
fileSizeBytes: number;
|
||||
createdAt: Date;
|
||||
@@ -2853,7 +2878,7 @@ export class InstructorService {
|
||||
fileSizeBytes: reference.fileSizeBytes,
|
||||
week,
|
||||
weekNumber: week,
|
||||
downloadUrl: `/curriculum/references/${reference.id}/download`,
|
||||
downloadUrl: this.r2PublicFileService.publicUrl(reference.storedFileName) ?? `/curriculum/references/${reference.id}/download`,
|
||||
createdAt: reference.createdAt.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
import { DeleteObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
type UploadPublicFileInput = {
|
||||
key: string;
|
||||
body: Buffer;
|
||||
contentType?: string | null;
|
||||
fileName?: string | null;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class R2PublicFileService {
|
||||
private client?: S3Client;
|
||||
|
||||
constructor(private readonly configService: ConfigService) {}
|
||||
|
||||
isConfigured() {
|
||||
return Boolean(
|
||||
this.bucket
|
||||
&& this.publicBaseUrl
|
||||
&& this.accessKeyId
|
||||
&& this.secretAccessKey,
|
||||
);
|
||||
}
|
||||
|
||||
createLmsCourseFileKey(input: {
|
||||
courseId: string;
|
||||
week: number;
|
||||
kind: 'REFERENCE' | 'EXAMPLE_CODE';
|
||||
fileId: string;
|
||||
extension?: string;
|
||||
}) {
|
||||
const section = input.kind === 'EXAMPLE_CODE' ? 'example-codes' : 'references';
|
||||
return [
|
||||
this.publicPrefix,
|
||||
'lms',
|
||||
'courses',
|
||||
input.courseId,
|
||||
'weeks',
|
||||
String(input.week),
|
||||
section,
|
||||
`${input.fileId}${input.extension ?? ''}`,
|
||||
].filter(Boolean).join('/');
|
||||
}
|
||||
|
||||
async uploadPublicFile(input: UploadPublicFileInput) {
|
||||
if (!this.isConfigured()) return null;
|
||||
|
||||
await this.getClient().send(new PutObjectCommand({
|
||||
Bucket: this.bucket,
|
||||
Key: input.key,
|
||||
Body: input.body,
|
||||
ContentLength: input.body.length,
|
||||
ContentType: input.contentType ?? 'application/octet-stream',
|
||||
ContentDisposition: input.fileName ? contentDispositionForFile(input.fileName) : undefined,
|
||||
}));
|
||||
|
||||
return this.publicUrl(input.key);
|
||||
}
|
||||
|
||||
async deletePublicFile(key: string) {
|
||||
if (!this.isConfigured() || !this.isPublicKey(key)) return;
|
||||
|
||||
await this.getClient().send(new DeleteObjectCommand({
|
||||
Bucket: this.bucket,
|
||||
Key: key,
|
||||
}));
|
||||
}
|
||||
|
||||
publicUrl(key: string) {
|
||||
if (!this.publicBaseUrl || !this.isPublicKey(key)) return null;
|
||||
return `${this.publicBaseUrl.replace(/\/$/, '')}/${encodeObjectKey(key)}`;
|
||||
}
|
||||
|
||||
isPublicKey(value: string) {
|
||||
return value.startsWith(`${this.publicPrefix}/`);
|
||||
}
|
||||
|
||||
private getClient() {
|
||||
if (this.client) return this.client;
|
||||
|
||||
const endpoint = this.configService.get<string>('R2_ENDPOINT')
|
||||
|| `https://${this.configService.get<string>('R2_ACCOUNT_ID')}.r2.cloudflarestorage.com`;
|
||||
|
||||
this.client = new S3Client({
|
||||
region: this.configService.get<string>('R2_REGION', 'auto'),
|
||||
endpoint,
|
||||
credentials: {
|
||||
accessKeyId: this.accessKeyId,
|
||||
secretAccessKey: this.secretAccessKey,
|
||||
},
|
||||
forcePathStyle: this.configService.get<string>('R2_FORCE_PATH_STYLE', 'true') !== 'false',
|
||||
});
|
||||
|
||||
return this.client;
|
||||
}
|
||||
|
||||
private get bucket() {
|
||||
return this.configService.get<string>('R2_BUCKET')?.trim() ?? '';
|
||||
}
|
||||
|
||||
private get publicBaseUrl() {
|
||||
return this.configService.get<string>('R2_PUBLIC_BASE_URL')?.trim() ?? '';
|
||||
}
|
||||
|
||||
private get accessKeyId() {
|
||||
return this.configService.get<string>('R2_ACCESS_KEY_ID')?.trim() ?? '';
|
||||
}
|
||||
|
||||
private get secretAccessKey() {
|
||||
return this.configService.get<string>('R2_SECRET_ACCESS_KEY')?.trim() ?? '';
|
||||
}
|
||||
|
||||
private get publicPrefix() {
|
||||
return this.configService.get<string>('R2_PUBLIC_FILE_PREFIX', 'studio-files/public').replace(/^\/+|\/+$/g, '');
|
||||
}
|
||||
}
|
||||
|
||||
function encodeObjectKey(key: string) {
|
||||
return key.split('/').map(encodeURIComponent).join('/');
|
||||
}
|
||||
|
||||
function contentDispositionForFile(fileName: string) {
|
||||
const fallbackName = fileName.replace(/[\\\"\r\n]/g, '_');
|
||||
return `attachment; filename="${fallbackName}"; filename*=UTF-8''${encodeURIComponent(fileName)}`;
|
||||
}
|
||||
Reference in New Issue
Block a user