diff --git a/backend/package-lock.json b/backend/package-lock.json index 943f37d..7492b2b 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -13,7 +13,8 @@ "@hono/zod-openapi": "^0.18.4", "dotenv": "^16.4.7", "hono": "^4.7.1", - "mysql2": "^3.12.0" + "mysql2": "^3.12.0", + "sharp": "^0.33.5" }, "devDependencies": { "@types/node": "^20.11.17", @@ -881,6 +882,16 @@ "node": ">=18.0.0" } }, + "node_modules/@emnapi/runtime": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/darwin-x64": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", @@ -1238,6 +1249,367 @@ "zod": "^3.19.1" } }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@smithy/abort-controller": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.1.tgz", @@ -1978,6 +2350,47 @@ "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", "license": "MIT" }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/denque": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", @@ -1986,6 +2399,15 @@ "node": ">=0.10" } }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/dotenv": { "version": "16.4.7", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", @@ -2097,6 +2519,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, "node_modules/is-property": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", @@ -2181,11 +2609,71 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/seq-queue": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, "node_modules/sqlstring": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", diff --git a/backend/package.json b/backend/package.json index eed326f..ddc476e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -5,14 +5,15 @@ "dev": "tsx watch src/index.ts" }, "dependencies": { - "@google/generative-ai": "^0.21.0", "@aws-sdk/client-s3": "^3.749.0", + "@google/generative-ai": "^0.21.0", "@hono/node-server": "^1.13.8", "@hono/swagger-ui": "^0.5.0", "@hono/zod-openapi": "^0.18.4", "dotenv": "^16.4.7", "hono": "^4.7.1", - "mysql2": "^3.12.0" + "mysql2": "^3.12.0", + "sharp": "^0.33.5" }, "devDependencies": { "@types/node": "^20.11.17", diff --git a/backend/src/controllers/Miyabi/createMiyabiHandler.ts b/backend/src/controllers/Miyabi/createMiyabiHandler.ts index 5907e25..9b4b178 100644 --- a/backend/src/controllers/Miyabi/createMiyabiHandler.ts +++ b/backend/src/controllers/Miyabi/createMiyabiHandler.ts @@ -10,12 +10,13 @@ type createMiyabiSchema = z.infer; const createMiyabiHandler: RouteHandler = async (c: Context) => { try { // 受け取ったjsonを各変数に格納 - const { user_icon, post_id } = await c.req.json(); + const { my_icon, post_id } = await c.req.json(); // 投稿が存在するかチェック const checkSql = `SELECT * FROM ${env.POSTS_TABLE_NAME} WHERE id = :post_id;`; const existingPosts = await db.query(checkSql, { post_id }); if (existingPosts.length == 0) { + console.log('投稿が見つかりません.'); return c.json( { message: '投稿が見つかりません.', @@ -27,10 +28,11 @@ const createMiyabiHandler: RouteHandler = async (c } // ここからDBのmiyabiテーブルへ追加処理 - const sql = `insert into ${env.MIYABI_TABLE_NAME} (user_icon, post_id) values (:user_icon, :post_id)`; - await db.query(sql, { user_icon, post_id }); + const sql = `insert into ${env.MIYABI_TABLE_NAME} (user_icon, post_id) values (:my_icon, :post_id)`; + await db.query(sql, { my_icon, post_id }); // レスポンス + console.log('雅しました.'); return c.json( { message: '雅しました.', @@ -38,6 +40,7 @@ const createMiyabiHandler: RouteHandler = async (c 200 ); } catch (err) { + console.log('雅に失敗しました.'); return c.json( { message: '雅に失敗しました.', diff --git a/backend/src/controllers/Miyabi/deleteMiyabiHandler.ts b/backend/src/controllers/Miyabi/deleteMiyabiHandler.ts index bcf4366..4dd38c8 100644 --- a/backend/src/controllers/Miyabi/deleteMiyabiHandler.ts +++ b/backend/src/controllers/Miyabi/deleteMiyabiHandler.ts @@ -10,12 +10,13 @@ type deleteMiyabiSchema = z.infer; const deleteMiyabiHandler: RouteHandler = async (c: Context) => { try { // 受け取ったjsonを各変数に格納 - const { user_icon, post_id } = await c.req.json(); + const { my_icon, post_id } = await c.req.json(); // 投稿が存在するかチェック const checkSql = `SELECT * FROM ${env.POSTS_TABLE_NAME} WHERE id = :post_id;`; const existingPosts = await db.query(checkSql, { post_id }); if (existingPosts.length == 0) { + console.log('投稿が見つかりません.'); return c.json( { message: '投稿が見つかりません.', @@ -27,10 +28,11 @@ const deleteMiyabiHandler: RouteHandler = async (c } // ここからDBのmiyabiテーブルから削除処理 - const sql = `DELETE FROM ${env.MIYABI_TABLE_NAME} WHERE post_id = :post_id AND user_icon = :user_icon;`; - await db.query(sql, { user_icon, post_id }); + const sql = `DELETE FROM ${env.MIYABI_TABLE_NAME} WHERE post_id = :post_id AND user_icon = :my_icon;`; + await db.query(sql, { my_icon, post_id }); // レスポンス + console.log('雅を削除しました.'); return c.json( { message: '雅を削除しました.', @@ -38,6 +40,7 @@ const deleteMiyabiHandler: RouteHandler = async (c 200 ); } catch (err) { + console.log('雅の削除に失敗しました.'); return c.json( { message: '雅の削除に失敗しました.', diff --git a/backend/src/controllers/Post/createPostHandler.ts b/backend/src/controllers/Post/createPostHandler.ts index 4461459..6b80915 100644 --- a/backend/src/controllers/Post/createPostHandler.ts +++ b/backend/src/controllers/Post/createPostHandler.ts @@ -8,6 +8,8 @@ import generateTanka from '../../lib/gemini.js'; import { sampleUploadSchema } from '../../schema/sampleS3Schema.js'; import type { sampleS3UploadRoute } from '../../routes/sampleS3Route.js'; import { uploadFile } from '../../lib/s3-connector.js'; +import path from 'path'; +import sharp from 'sharp'; type createPostSchema = z.infer; @@ -23,7 +25,7 @@ const createPostHandler: RouteHandler = async (c: Co //console.log(image); if (!originalValue || typeof originalValue !== 'string') { - //console.log('if'); + console.log('originalはstringである必要があります.'); return c.json( { message: 'originalはstringである必要があります.', @@ -39,6 +41,7 @@ const createPostHandler: RouteHandler = async (c: Co // tankaArrayが空([])ならエラーを返す if (tankaArray.length == 0) { + console.log('tankaが空です.'); return c.json( { message: 'tankaが空です.', @@ -56,23 +59,38 @@ const createPostHandler: RouteHandler = async (c: Co if (image == null) { image_path = null; } else { - // ここに圧縮処理 (jpegにして解像度さげる.めざせ500KB) + // ここに圧縮処理 (jpegにしてqualityさげる.めざせ500KB) + // File型からbufferへ + const arrayBuffer = await image.arrayBuffer(); + const buffer = Buffer.from(arrayBuffer); + const new_file_name = 'file.jpg'; - // アップロード - image_path = await uploadFile(image); + await compressImage(buffer) + .then(async (resultBuffer) => { + // BufferからFile型へ変換 + const file = new File([resultBuffer], new_file_name, { type: 'image/jpeg' }); + // アップロード + image_path = await uploadFile(file); + }) + .catch((err) => { + console.error('画像圧縮エラー:', err); + return c.json( + { + message: '投稿に失敗しました.', + statusCode: 500, + error: 'Internal Server Error', + }, + 500 + ); + }); } - //console.log(original); - //console.log(tanka); - //console.log(image_path); - //console.log(user_name); - //console.log(user_icon); - // ここからDBのpostテーブルへ情報登録 const sql = `insert into ${env.POSTS_TABLE_NAME} (original, tanka, image_path, user_name, user_icon) values (:original, :tanka, :image_path, :user_name, :user_icon)`; await db.query(sql, { original, tanka, image_path, user_name, user_icon }); // レスポンス + console.log('投稿しました.'); return c.json( { message: '投稿しました.', @@ -81,6 +99,7 @@ const createPostHandler: RouteHandler = async (c: Co 200 ); } catch (err) { + console.log('投稿に失敗しました.'); return c.json( { message: '投稿に失敗しました.', @@ -93,3 +112,49 @@ const createPostHandler: RouteHandler = async (c: Co }; export default createPostHandler; + +// 1080pに圧縮.それでも500KB超えていたら二分探索で500KB以下にする +async function compressImage(inputBuffer: Buffer): Promise { + let minQuality = 1; + let maxQuality = 100; + let bestQuality = maxQuality; + let bestBuffer: Buffer | null = null; + const targetFileSize = 500 * 1024; // 500KB + + // jpegに変換して,それ以外何もしなくても500KB以下か? + const compressedJpegBuffer = await sharp(inputBuffer).jpeg().toBuffer(); + if (compressedJpegBuffer.length <= targetFileSize) { + return compressedJpegBuffer; + } + + // 1080pにして500KB以下か? + const compressed1080pBuffer = await sharp(inputBuffer).resize({ height: 1080 }).jpeg().toBuffer(); + if (compressed1080pBuffer.length <= targetFileSize) { + return compressed1080pBuffer; + } + + // 二分探索 + while (minQuality <= maxQuality) { + const midQuality = Math.floor((minQuality + maxQuality) / 2); + const compressedBuffer = await sharp(inputBuffer) + .resize({ height: 1080 }) + .jpeg({ quality: midQuality }) + .toBuffer(); + + // サイズがtargetFileSizeより大きければ,maxQualityを小さく + if (compressedBuffer.length > targetFileSize) { + maxQuality = midQuality - 1; + } else { + // サイズがtargetFileSizeより小さければ,一旦bestBufferとし,minQualityを大きく + bestBuffer = compressedBuffer; + bestQuality = midQuality; + minQuality = midQuality + 1; + } + } + + if (!bestBuffer) { + throw new Error('画像の圧縮に失敗しました.'); + } + + return bestBuffer; +} diff --git a/backend/src/controllers/Post/deletePostHandler.ts b/backend/src/controllers/Post/deletePostHandler.ts index e2dfc32..ecb2acb 100644 --- a/backend/src/controllers/Post/deletePostHandler.ts +++ b/backend/src/controllers/Post/deletePostHandler.ts @@ -16,6 +16,7 @@ const deletePostHandler: RouteHandler = async (c: Co const checkSql = `SELECT * FROM ${env.POSTS_TABLE_NAME} WHERE id = :post_id;`; const existingPosts = await db.query(checkSql, { post_id }); if (existingPosts.length == 0) { + console.log('投稿が見つかりません.'); return c.json( { message: '投稿が見つかりません.', @@ -30,6 +31,7 @@ const deletePostHandler: RouteHandler = async (c: Co const checkSql2 = `SELECT * FROM ${env.POSTS_TABLE_NAME} WHERE id = :post_id;`; const postInfo = await db.query(checkSql2, { post_id }); if (postInfo[0].user_icon != user_icon) { + console.log('許可がありません.'); return c.json( { message: '許可がありません.', @@ -46,16 +48,18 @@ const deletePostHandler: RouteHandler = async (c: Co await db.query(sql, { user_icon, post_id }); // レスポンス + console.log('投稿を削除しました.'); return c.json( { - message: '投稿しました.', + message: '投稿を削除しました.', }, 200 ); } catch (err) { + console.log('投稿の削除に失敗しました.'); return c.json( { - message: '投稿に失敗しました.', + message: '投稿の削除に失敗しました.', statusCode: 500, error: 'Internal Server Error', }, diff --git a/backend/src/controllers/Post/getPostHandler.ts b/backend/src/controllers/Post/getPostHandler.ts index fc48e9b..cead28d 100644 --- a/backend/src/controllers/Post/getPostHandler.ts +++ b/backend/src/controllers/Post/getPostHandler.ts @@ -35,6 +35,7 @@ const getPostHandler: RouteHandler = async (c: Context) const checkSql = `SELECT * FROM ${env.POSTS_TABLE_NAME} WHERE id = :post_id;`; const existingPosts = await db.query(checkSql, { post_id }); if (existingPosts.length == 0) { + console.log('投稿が見つかりません.'); return c.json( { message: '投稿が見つかりません.', @@ -92,6 +93,7 @@ const getPostHandler: RouteHandler = async (c: Context) //console.log(results); // レスポンス + console.log('投稿を取得しました.'); return c.json( { message: '投稿を取得しました.', @@ -100,6 +102,7 @@ const getPostHandler: RouteHandler = async (c: Context) 200 ); } catch (err) { + console.log('投稿の取得に失敗しました.'); return c.json( { message: '投稿の取得に失敗しました.', diff --git a/backend/src/schema/Miyabi/createMiyabiSchema.ts b/backend/src/schema/Miyabi/createMiyabiSchema.ts index 4ac22b2..4385a25 100644 --- a/backend/src/schema/Miyabi/createMiyabiSchema.ts +++ b/backend/src/schema/Miyabi/createMiyabiSchema.ts @@ -2,7 +2,7 @@ import { z } from '@hono/zod-openapi'; // リクエストの型 export const createMiyabiSchema = z.object({ - user_icon: z.string().openapi({ + my_icon: z.string().openapi({ example: 'https://avatars.githubusercontent.com/u/131171129?v=4', description: 'git hubのアイコンURL', }), diff --git a/backend/src/schema/Miyabi/deleteMiyabiSchema.ts b/backend/src/schema/Miyabi/deleteMiyabiSchema.ts index 2d78c54..8575ca6 100644 --- a/backend/src/schema/Miyabi/deleteMiyabiSchema.ts +++ b/backend/src/schema/Miyabi/deleteMiyabiSchema.ts @@ -2,7 +2,7 @@ import { z } from '@hono/zod-openapi'; // リクエストの型 export const deleteMiyabiSchema = z.object({ - user_icon: z.string().openapi({ + my_icon: z.string().openapi({ example: 'https://avatars.githubusercontent.com/u/131171129?v=4', description: 'git hubのアイコンURL', }), diff --git a/backend/src/schema/Miyabi/getMiyabiRankingSchema.ts b/backend/src/schema/Miyabi/getMiyabiRankingSchema.ts new file mode 100644 index 0000000..a179fde --- /dev/null +++ b/backend/src/schema/Miyabi/getMiyabiRankingSchema.ts @@ -0,0 +1,37 @@ +import { z } from '@hono/zod-openapi'; + +// リクエストの型 +export const getMiyabiRankingSchema = z.object({ + limit: z.number().openapi({ + example: 10, + description: '取得する投稿の数', + }), + my_icon: z.string().optional().openapi({ + example: 'https://avatars.githubusercontent.com/u/131171129?v=4', + description: 'git hubのアイコンURL', + }), + post_id: z.string().optional().openapi({ + example: 'cb3adc47-eba3-11ef-9ce7-0242ac130002', + description: '投稿id', + }), +}); + +// postのスキーマ +export const postSchema = z.object({ + rank: z.number(), + id: z.string(), + original: z.string(), + tanka: z.array(z.string()), + image_path: z.string(), + created_at: z.string(), + user_name: z.string(), + user_icon: z.string(), + miyabi_count: z.number(), + is_miyabi: z.boolean(), +}); + +// レスポンスの型 +export const getMiyabiRankingResponseSchema = z.object({ + message: z.string(), + posts: z.array(postSchema), +}); diff --git a/frontend/src/app/timeline/actions/countMiyabi.ts b/frontend/src/app/timeline/actions/countMiyabi.ts new file mode 100644 index 0000000..cc818a3 --- /dev/null +++ b/frontend/src/app/timeline/actions/countMiyabi.ts @@ -0,0 +1,59 @@ +// サーバアクション +'use server'; + +import { hc } from 'hono/client'; +import { AppType } from '../../../../../backend/src/index'; + +const client = hc(process.env.BACKEND_URL ?? 'http://localhost:8080'); + +/** + * 雅を増やす非同期関数 + * @async + * @function addMiyabi + * @param {Object} params - 雅追加のためのパラメータオブジェクト + * @param {string} params.iconUrl - ユーザのアイコン画像URL + * @param {string} params.postId - 雅する投稿のID + */ +export const addMiyabi = async ({ iconUrl, postId }: { iconUrl: string; postId: string }) => { + try { + const res = await client.miyabi.$post({ + json: { + post_id: postId, + my_icon: iconUrl, + }, + }); + + // エラーがある場合はログを出力 + if (!res.ok) { + console.log(res.statusText); + } + } catch (error) { + console.error(error); + } +}; + +/** + * 雅を減らす非同期関数 + * @async + * @function removeMiyabi + * @param {Object} params - 雅追加のためのパラメータオブジェクト + * @param {string} params.iconUrl - ユーザのアイコン画像URL + * @param {string} params.postId - 雅する投稿のID + */ +export const removeMiyabi = async ({ iconUrl, postId }: { iconUrl: string; postId: string }) => { + try { + const res = await client.miyabi.$delete({ + json: { + post_id: postId, + my_icon: iconUrl, + }, + }); + + // エラーがある場合はログを出力 + if (!res.ok) { + console.log(res.statusText); + } + } catch (error) { + console.error(error); + } +}; diff --git a/frontend/src/app/timeline/actions/deletePost.ts b/frontend/src/app/timeline/actions/deletePost.ts new file mode 100644 index 0000000..f3e6dc5 --- /dev/null +++ b/frontend/src/app/timeline/actions/deletePost.ts @@ -0,0 +1,45 @@ +// サーバアクション +'use server'; + +import { hc } from 'hono/client'; +import { AppType } from '../../../../../backend/src/index'; + +const client = hc(process.env.BACKEND_URL ?? 'http://localhost:8080'); + +/** + * 投稿データを削除する非同期関数 + * @async + * @function deletePost + * @param {Object} params - 投稿データ取得のためのパラメータオブジェクト + * @param {string} params.iconUrl - ユーザのアイコン画像URL + * @param {string} params.postId - 削除する投稿のID + * @returns {Promise} 結果を返すPromise. + */ +const deletePost = async ({ + iconUrl, + postId, +}: { + iconUrl: string; + postId: string; +}): Promise => { + try { + const res = await client.post.$delete({ + json: { + user_icon: iconUrl, + post_id: postId, + }, + }); + + // エラーがある場合はログを出力 + if (!res.ok) { + console.log(res.statusText); + return false; + } + return true; + } catch (error) { + console.error(error); + return false; + } +}; + +export default deletePost; diff --git a/frontend/src/app/timeline/actions/fetchPosts.ts b/frontend/src/app/timeline/actions/fetchPosts.ts index 72dabe3..456c80d 100644 --- a/frontend/src/app/timeline/actions/fetchPosts.ts +++ b/frontend/src/app/timeline/actions/fetchPosts.ts @@ -34,7 +34,9 @@ const fetchPosts = async ({ post_id: offsetId, }, }); - console.log(`Loading more Posts... limit: ${limit}, offsetId: ${offsetId}`); + console.log( + `Loading more Posts...\nlimit: ${limit}\niconUrl: ${iconUrl}\noffsetId: ${offsetId}` + ); // エラーがある場合は空の配列を返す if (!res.ok) { @@ -44,7 +46,6 @@ const fetchPosts = async ({ const json = await res.json(); return json.posts.map((post) => ({ - ...post, id: post.id, tanka: post.tanka, original: post.original, diff --git a/frontend/src/app/timeline/page.tsx b/frontend/src/app/timeline/page.tsx index 7f670c0..03905ba 100644 --- a/frontend/src/app/timeline/page.tsx +++ b/frontend/src/app/timeline/page.tsx @@ -13,13 +13,14 @@ import { useSession } from 'next-auth/react'; import LoginDialog from '@/components/LoginDialog'; import { useRouter } from 'next/navigation'; -const LIMIT = 10; +const LIMIT = 10; // 一度に取得する投稿数 +const MAX = 100; // タイムラインに表示できる最大投稿数 const Timeline = () => { // 投稿データの配列 const [posts, setPosts] = useState([]); // 投稿取得時のオフセットID - const [offsetId, setOffsetId] = useState(''); + const offsetIdRef = useRef(''); // データ取得中かどうかのフラグ const [isLoading, setIsLoading] = useState(false); // これ以上取得できる投稿があるかのフラグ @@ -36,6 +37,8 @@ const Timeline = () => { //IntersectionObserverを保持するためのref const observer = useRef(null); + // 投稿取得の重複実行を防ぐためのref + const isFetchingRef = useRef(false); /** * 追加の投稿データを取得し,状態を更新する非同期関数のCallback Ref @@ -44,16 +47,24 @@ const Timeline = () => { * @returns {Promise} 投稿データの取得と状態更新が完了するPromise */ const loadMorePosts = useCallback(async () => { + if (isFetchingRef.current) return; + isFetchingRef.current = true; setIsLoading(true); // 投稿データを取得 const newPosts = await fetchPosts({ limit: LIMIT, - iconUrl: session.data?.user?.id, - offsetId: offsetId, + iconUrl: session.data?.user?.image ?? '', + offsetId: offsetIdRef.current, }); if (newPosts && newPosts.length > 0) { - setPosts((prevPosts) => [...prevPosts, ...newPosts]); - setOffsetId(() => newPosts[newPosts.length - 1].id); + setPosts((prevPosts) => { + const updatedPosts = [...prevPosts, ...newPosts]; + if (updatedPosts.length >= MAX) { + setHasMore(false); + } + return updatedPosts; + }); + offsetIdRef.current = newPosts[newPosts.length - 1].id; // 取得した投稿数がLIMIT未満の場合は,これ以上取得できる投稿は無い. if (newPosts.length < LIMIT) { setHasMore(false); @@ -63,7 +74,8 @@ const Timeline = () => { setHasMore(false); } setIsLoading(false); - }, [offsetId]); + isFetchingRef.current = false; + }, [session.data?.user?.image]); // ターゲットの要素を監視するためのcallback ref const targetRef = useCallback( @@ -94,6 +106,11 @@ const Timeline = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + // 見かけ上の投稿を削除する関数 + const deletePost = (postId: string) => { + setPosts((prevPosts) => prevPosts.filter((post) => post.id !== postId)); + }; + return (
{/* ヘッダ */} @@ -117,10 +134,10 @@ const Timeline = () => {
- + {isLoading &&

投稿を取得中...

}
- {!hasMore &&

これ以上投稿はありません

} + {!hasMore &&

これ以上投稿を取得できません。

}
@@ -146,7 +163,7 @@ const Timeline = () => { )} {/* ログイン確認ダイアログ表示が有効の場合,ダイアログを表示する */} - {loginDialogOpen && } +
); }; diff --git a/frontend/src/app/yomu/AfterYomu.tsx b/frontend/src/app/yomu/AfterYomu.tsx index bd892fb..b5f977d 100644 --- a/frontend/src/app/yomu/AfterYomu.tsx +++ b/frontend/src/app/yomu/AfterYomu.tsx @@ -136,7 +136,7 @@ const AfterYomu = ({ tanka, imagePath, userName, userIconPath }: AfterYomuProps) return (
void; onCancel?: () => void; className?: string; @@ -21,6 +22,7 @@ const GifButton = ({ afterSrc, animationDuration, initialIsClicked, + isAnimationDisabled = false, onClick, onCancel, className, @@ -32,6 +34,7 @@ const GifButton = ({ const [isAnimationPlaying, setIsAnimationPlaying] = useState(false); const clickButton = () => { + if (isAnimationDisabled) return; setIsAnimationPlaying(true); setTimeout(() => { setIsAnimationPlaying(false); @@ -40,6 +43,7 @@ const GifButton = ({ }; const cancelButton = () => { + if (isAnimationDisabled) return; setIsAnimationPlaying(false); setIsClicked(false); }; diff --git a/frontend/src/components/MiyabiButton.tsx b/frontend/src/components/MiyabiButton.tsx index 57426dd..909f11d 100644 --- a/frontend/src/components/MiyabiButton.tsx +++ b/frontend/src/components/MiyabiButton.tsx @@ -6,6 +6,7 @@ interface MiyabiButtonProps { size?: 'small' | 'medium' | 'large'; className?: string; initialIsClicked?: boolean; + isAnimationDisabled?: boolean; onClick?: () => void; onCancel?: () => void; } @@ -16,6 +17,7 @@ const MiyabiButton = ({ onCancel, className, initialIsClicked = false, + isAnimationDisabled = false, }: MiyabiButtonProps) => { return ( ); diff --git a/frontend/src/components/Post.tsx b/frontend/src/components/Post.tsx index 73f5092..32cda07 100644 --- a/frontend/src/components/Post.tsx +++ b/frontend/src/components/Post.tsx @@ -13,11 +13,14 @@ import { MdDeleteForever } from 'react-icons/md'; import { useSession } from 'next-auth/react'; import Dialog from '@/components/Dialog'; import LoginDialog from './LoginDialog'; +import { addMiyabi, removeMiyabi } from '@/app/timeline/actions/countMiyabi'; +import deletePost from '@/app/timeline/actions/deletePost'; // props の型定義 interface PostProps { post: PostTypes; className?: string; + onDelete: (postId: string) => void; } /** @@ -26,7 +29,7 @@ interface PostProps { * @param {PostProps} props - 投稿データを含むオブジェクト * @return {JSX.Element} 投稿を表示するReactコンポーネント */ -const Post = ({ post, className }: PostProps) => { +const Post = ({ post, className, onDelete }: PostProps) => { // 短歌をパースする const tanka = parseTanka(post.tanka); // 投稿に画像が含まれるか @@ -37,6 +40,8 @@ const Post = ({ post, className }: PostProps) => { const [modalOpen, setModalOpen] = useState(false); // 削除確認ダイアログの表示状態 const [dialogOpen, setDialogOpen] = useState(false); + // 削除失敗ダイアログの表示状態 + const [deleteFailedDialogOpen, setDeleteFailedDialogOpen] = useState(false); // ユーザアイコンURLが一致するなら自分の投稿 const isMyPost = useSession().data?.user?.image === post.user.iconUrl; // ドロップダウンメニューの要素 @@ -59,6 +64,10 @@ const Post = ({ post, className }: PostProps) => { const isLoggedIn = session.status === 'authenticated'; // ログイン促進ダイアログの開閉状態 const [loginDialogOpen, setLoginDialogOpen] = useState(false); + // 親の持つPostsから自身を削除する + const handleDelete = () => { + onDelete(post.id); + }; return (
@@ -116,47 +125,65 @@ const Post = ({ post, className }: PostProps) => {

{miyabiCount.toLocaleString()}

{ + onClick={async () => { if (isLoggedIn) { setMiyabiCount((count) => ++count); + await addMiyabi({ postId: post.id, iconUrl: session.data?.user?.image ?? '' }); } else { setLoginDialogOpen(true); } }} - onCancel={() => { + onCancel={async () => { if (isLoggedIn) { setMiyabiCount((count) => --count); + await removeMiyabi({ postId: post.id, iconUrl: session.data?.user?.image ?? '' }); } else { setLoginDialogOpen(true); } }} initialIsClicked={post.miyabiIsClicked} + isAnimationDisabled={!isLoggedIn} className='mr-0' />
{/* 拡大表示が有効の場合,モーダルを表示する */} {modalOpen && } - {/* ダイアログ表示が有効の場合,ダイアログを表示する */} - {dialogOpen && ( - { - console.log('はい'); - setDialogOpen(false); - }} - noCallback={() => { - console.log('いいえ'); - setDialogOpen(false); - }} - yesText='はい' - noText='いいえ' - /> - )} + {/* 削除確認ダイアログ表示が有効の場合,ダイアログを表示する */} + { + console.log('はい'); + setDialogOpen(false); + const result = await deletePost({ + postId: post.id, + iconUrl: session.data?.user?.image ?? '', + }); + if (!result) setDeleteFailedDialogOpen(true); + else handleDelete(); + }} + noCallback={() => { + console.log('いいえ'); + setDialogOpen(false); + }} + yesText='はい' + noText='いいえ' + /> + {/* 削除失敗ダイアログ表示が有効の場合,ダイアログを表示する */} + { + setDeleteFailedDialogOpen(false); + }} + yesText='はい' + isOnlyOK + /> {/* ログイン確認ダイアログ表示が有効の場合,ダイアログを表示する */} - {loginDialogOpen && } +
); }; diff --git a/frontend/src/components/PostList.tsx b/frontend/src/components/PostList.tsx index 0eaeccb..9b64a6d 100644 --- a/frontend/src/components/PostList.tsx +++ b/frontend/src/components/PostList.tsx @@ -8,6 +8,7 @@ import Post from '@/components/Post'; interface PostListProps { posts: PostTypes[]; className?: string; + onDelete: (postId: string) => void; } /** @@ -16,11 +17,11 @@ interface PostListProps { * @param {PostListProps} props - 投稿データの配列を含むオブジェクト * @return {JSX.Elements} 投稿リスト表示するReactコンポーネント */ -const PostList = ({ posts, className }: PostListProps) => { +const PostList = ({ posts, className, onDelete }: PostListProps) => { return (
{posts.map((post, i) => ( - + ))}
); diff --git a/frontend/src/components/SideMenu.tsx b/frontend/src/components/SideMenu.tsx index babb009..555d724 100644 --- a/frontend/src/components/SideMenu.tsx +++ b/frontend/src/components/SideMenu.tsx @@ -87,24 +87,22 @@ const SideMenu = ({ className, style }: SideMenuProps) => { )} {/* ログアウト確認ダイアログ表示が有効の場合,ダイアログを表示する */} - {logoutDialogOpen && ( - { - setLogoutDialogOpen(false); - signOut(); - }} - noCallback={() => { - setLogoutDialogOpen(false); - }} - yesText='はい' - noText='いいえ' - /> - )} + { + setLogoutDialogOpen(false); + signOut(); + }} + noCallback={() => { + setLogoutDialogOpen(false); + }} + yesText='はい' + noText='いいえ' + /> {/* ログイン確認ダイアログ表示が有効の場合,ダイアログを表示する */} - {loginDialogOpen && } + ); };