From ebacf6e1af6b89102dc0793e71ec79a7c4cb1d84 Mon Sep 17 00:00:00 2001 From: Last2014 Date: Sun, 13 Jul 2025 01:06:42 +0900 Subject: [PATCH] =?UTF-8?q?config=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E3=81=AB=E7=A7=BB=E8=A1=8C=E3=83=BB=E5=A4=9A=E8=A8=80=E8=AA=9E?= =?UTF-8?q?=E5=8C=96=E5=AE=8C=E5=85=A8=E5=AF=BE=E5=BF=9C=E3=83=BBmanifest?= =?UTF-8?q?=E7=B3=BB=E5=AF=BE=E5=BF=9C=E3=83=BBPWA=E5=AF=BE=E5=BF=9C?= =?UTF-8?q?=E3=83=BB=E3=83=AD=E3=83=BC=E3=83=89=E8=A1=A8=E7=A4=BA=E3=83=BB?= =?UTF-8?q?=E3=82=A2=E3=82=AB=E3=82=A6=E3=83=B3=E3=83=88=E5=88=9D=E6=9C=9F?= =?UTF-8?q?=E5=8C=96=E5=87=A6=E7=90=86=E5=AE=8C=E6=88=90=E3=83=BBmailsend.?= =?UTF-8?q?ts=E3=81=AE=E5=9E=8B=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 13 -- .gitignore | 9 +- README.md | 14 +- examples/peas.config.ts | 29 ++++ package-lock.json | 108 +++++++----- package.json | 5 +- src/app/api/mailverified/check/route.ts | 46 +++--- src/app/api/mailverified/send/route.ts | 121 +++++++------- src/app/api/signin/route.ts | 86 +++++----- src/app/api/signup/route.ts | 171 +++++++++++-------- src/app/bulma.css | 2 +- src/app/favicon.ico | Bin 25931 -> 15406 bytes src/app/layout.tsx | 14 +- src/app/mailverify/page.tsx | 115 ++++++++----- src/app/manifest.ts | 20 +++ src/app/page.tsx | 42 ++++- src/app/robots.ts | 20 +++ src/app/signin/page.tsx | 107 ++++++++---- src/app/signin/style.css | 6 +- src/app/signup/page.tsx | 211 +++++++++++++++++------- src/app/signup/style.css | 16 +- src/app/sitemap.ts | 5 + src/app/style.css | 17 +- src/lang/component/client.tsx | 50 +++--- src/lang/component/server.tsx | 18 +- src/lang/component/variable.ts | 29 ++-- src/lang/messages.ts | 133 ++++++++++----- src/lib/database.ts | 19 ++- src/lib/mailsend.ts | 91 +++++----- src/lib/peas.config.d.ts | 24 +++ 30 files changed, 987 insertions(+), 554 deletions(-) delete mode 100644 .env.example create mode 100644 examples/peas.config.ts create mode 100644 src/app/manifest.ts create mode 100644 src/app/robots.ts create mode 100644 src/app/sitemap.ts create mode 100644 src/lib/peas.config.d.ts diff --git a/.env.example b/.env.example deleted file mode 100644 index b3fe7e4..0000000 --- a/.env.example +++ /dev/null @@ -1,13 +0,0 @@ -// DATABASE(MySQL/MariaDB) -DB_HOST= -DB_PORT= -DB_USER= -DB_PASSWORD= -DB_DATABASE= - -// MAIL(SMTP) -SMTP_HOST= -SMTP_PORT= -SMTP_SECURE= -SMTP_USER= -SMTP_PASSWORD= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 44a4d13..0ccb2e0 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ !.yarn/plugins !.yarn/releases !.yarn/versions +/package-lock.json # testing /coverage @@ -31,8 +32,7 @@ yarn-error.log* .pnpm-debug.log* # env files (can opt-in for committing if needed) -.env -.env.local +.env* # vercel .vercel @@ -44,4 +44,7 @@ next-env.d.ts # editor /.vscode/ /.trae/ -/.vs/ \ No newline at end of file +/.vs/ + +# config +/peas.config.ts diff --git a/README.md b/README.md index 6989cff..41fddeb 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ -
- # Peas - + ## Technologys + @@ -20,11 +19,12 @@ ## About Peas -Peas is a SNS designed for sharing profiles. -You can register and share your user profile. -You can freely create profile sections suitable for use at school or work, and also change the privacy settings. + +Peas is a SNS designed for sharing profiles. +You can register and share your user profile. +You can freely create profile sections suitable for use at school or work, and also change the privacy settings. It also supports printing QR codes, making sharing easy. ## LICENSE + AGPL-v3.0 -
\ No newline at end of file diff --git a/examples/peas.config.ts b/examples/peas.config.ts new file mode 100644 index 0000000..c0eb9f0 --- /dev/null +++ b/examples/peas.config.ts @@ -0,0 +1,29 @@ +import { PeasConfigType } from "@/lib/peas.config"; + +const PeasConfig: PeasConfigType = { + // Server Config + serverName: "Peas Server", + serverDescription: "Peas SNS", + serverHost: "peas.example.com", + + // SSL Config + ssl: { + ssl: false, + }, + + // SMTP Config + smtpHost: "mail.example.com", + smtpPort: 587, + smtpSecure: false, + smtpUser: "info@mail.example.com", + smtpPassword: "SMTPMailPassword", + + // Database Config + databaseHost: "db.example.com", + databasePort: 3306, + databaseUser: "peas", + databasePassword: "DatabasePassword", + databaseDB: "peas", +}; + +export default PeasConfig; diff --git a/package-lock.json b/package-lock.json index cf45314..439ccbe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,15 @@ { "name": "peas", - "version": "1.0", + "version": "nonerelease", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "peas", - "version": "1.0", - "license": "ISC", + "version": "nonerelease", + "license": "AGPL-3.0-later", "dependencies": { + "@iconify/react": "^6.0.0", "@types/dotenv": "^6.1.1", "@types/eslint": "^9.6.1", "@types/js-cookie": "^3.0.6", @@ -19,7 +20,7 @@ "dotenv": "^16.5.0", "js-cookie": "^3.0.5", "mysql2": "^3.14.1", - "next": "^15.3.2", + "next": "^15.3.4", "nodemailer": "^7.0.3", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -289,6 +290,27 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@iconify/react": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@iconify/react/-/react-6.0.0.tgz", + "integrity": "sha512-eqNscABVZS8eCpZLU/L5F5UokMS9mnCf56iS1nM9YYHdH8ZxqZL9zyjSwW60IOQFsXZkilbBiv+1paMXBhSQnw==", + "license": "MIT", + "dependencies": { + "@iconify/types": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/cyberalien" + }, + "peerDependencies": { + "react": ">=16" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, "node_modules/@img/sharp-darwin-arm64": { "version": "0.34.2", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.2.tgz", @@ -724,9 +746,9 @@ } }, "node_modules/@next/env": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.2.tgz", - "integrity": "sha512-xURk++7P7qR9JG1jJtLzPzf0qEvqCN0A/T3DXf8IPMKo9/6FfjxtEffRJIIew/bIL4T3C2jLLqBor8B/zVlx6g==", + "version": "15.3.4", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.4.tgz", + "integrity": "sha512-ZkdYzBseS6UjYzz6ylVKPOK+//zLWvD6Ta+vpoye8cW11AjiQjGYVibF0xuvT4L0iJfAPfZLFidaEzAOywyOAQ==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -740,9 +762,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.2.tgz", - "integrity": "sha512-2DR6kY/OGcokbnCsjHpNeQblqCZ85/1j6njYSkzRdpLn5At7OkSdmk7WyAmB9G0k25+VgqVZ/u356OSoQZ3z0g==", + "version": "15.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.4.tgz", + "integrity": "sha512-z0qIYTONmPRbwHWvpyrFXJd5F9YWLCsw3Sjrzj2ZvMYy9NPQMPZ1NjOJh4ojr4oQzcGYwgJKfidzehaNa1BpEg==", "cpu": [ "arm64" ], @@ -756,9 +778,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.2.tgz", - "integrity": "sha512-ro/fdqaZWL6k1S/5CLv1I0DaZfDVJkWNaUU3un8Lg6m0YENWlDulmIWzV96Iou2wEYyEsZq51mwV8+XQXqMp3w==", + "version": "15.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.4.tgz", + "integrity": "sha512-Z0FYJM8lritw5Wq+vpHYuCIzIlEMjewG2aRkc3Hi2rcbULknYL/xqfpBL23jQnCSrDUGAo/AEv0Z+s2bff9Zkw==", "cpu": [ "x64" ], @@ -772,9 +794,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.2.tgz", - "integrity": "sha512-covwwtZYhlbRWK2HlYX9835qXum4xYZ3E2Mra1mdQ+0ICGoMiw1+nVAn4d9Bo7R3JqSmK1grMq/va+0cdh7bJA==", + "version": "15.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.4.tgz", + "integrity": "sha512-l8ZQOCCg7adwmsnFm8m5q9eIPAHdaB2F3cxhufYtVo84pymwKuWfpYTKcUiFcutJdp9xGHC+F1Uq3xnFU1B/7g==", "cpu": [ "arm64" ], @@ -788,9 +810,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.2.tgz", - "integrity": "sha512-KQkMEillvlW5Qk5mtGA/3Yz0/tzpNlSw6/3/ttsV1lNtMuOHcGii3zVeXZyi4EJmmLDKYcTcByV2wVsOhDt/zg==", + "version": "15.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.4.tgz", + "integrity": "sha512-wFyZ7X470YJQtpKot4xCY3gpdn8lE9nTlldG07/kJYexCUpX1piX+MBfZdvulo+t1yADFVEuzFfVHfklfEx8kw==", "cpu": [ "arm64" ], @@ -804,9 +826,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.2.tgz", - "integrity": "sha512-uRBo6THWei0chz+Y5j37qzx+BtoDRFIkDzZjlpCItBRXyMPIg079eIkOCl3aqr2tkxL4HFyJ4GHDes7W8HuAUg==", + "version": "15.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.4.tgz", + "integrity": "sha512-gEbH9rv9o7I12qPyvZNVTyP/PWKqOp8clvnoYZQiX800KkqsaJZuOXkWgMa7ANCCh/oEN2ZQheh3yH8/kWPSEg==", "cpu": [ "x64" ], @@ -820,9 +842,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.2.tgz", - "integrity": "sha512-+uxFlPuCNx/T9PdMClOqeE8USKzj8tVz37KflT3Kdbx/LOlZBRI2yxuIcmx1mPNK8DwSOMNCr4ureSet7eyC0w==", + "version": "15.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.4.tgz", + "integrity": "sha512-Cf8sr0ufuC/nu/yQ76AnarbSAXcwG/wj+1xFPNbyNo8ltA6kw5d5YqO8kQuwVIxk13SBdtgXrNyom3ZosHAy4A==", "cpu": [ "x64" ], @@ -836,9 +858,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.2.tgz", - "integrity": "sha512-LLTKmaI5cfD8dVzh5Vt7+OMo+AIOClEdIU/TSKbXXT2iScUTSxOGoBhfuv+FU8R9MLmrkIL1e2fBMkEEjYAtPQ==", + "version": "15.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.4.tgz", + "integrity": "sha512-ay5+qADDN3rwRbRpEhTOreOn1OyJIXS60tg9WMYTWCy3fB6rGoyjLVxc4dR9PYjEdR2iDYsaF5h03NA+XuYPQQ==", "cpu": [ "arm64" ], @@ -852,9 +874,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.2.tgz", - "integrity": "sha512-aW5B8wOPioJ4mBdMDXkt5f3j8pUr9W8AnlX0Df35uRWNT1Y6RIybxjnSUe+PhM+M1bwgyY8PHLmXZC6zT1o5tA==", + "version": "15.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.4.tgz", + "integrity": "sha512-4kDt31Bc9DGyYs41FTL1/kNpDeHyha2TC0j5sRRoKCyrhNcfZ/nRQkAUlF27mETwm8QyHqIjHJitfcza2Iykfg==", "cpu": [ "x64" ], @@ -4349,12 +4371,12 @@ "license": "MIT" }, "node_modules/next": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/next/-/next-15.3.2.tgz", - "integrity": "sha512-CA3BatMyHkxZ48sgOCLdVHjFU36N7TF1HhqAHLFOkV6buwZnvMI84Cug8xD56B9mCuKrqXnLn94417GrZ/jjCQ==", + "version": "15.3.4", + "resolved": "https://registry.npmjs.org/next/-/next-15.3.4.tgz", + "integrity": "sha512-mHKd50C+mCjam/gcnwqL1T1vPx/XQNFlXqFIVdgQdVAFY9iIQtY0IfaVflEYzKiqjeA7B0cYYMaCrmAYFjs4rA==", "license": "MIT", "dependencies": { - "@next/env": "15.3.2", + "@next/env": "15.3.4", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", @@ -4369,14 +4391,14 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.3.2", - "@next/swc-darwin-x64": "15.3.2", - "@next/swc-linux-arm64-gnu": "15.3.2", - "@next/swc-linux-arm64-musl": "15.3.2", - "@next/swc-linux-x64-gnu": "15.3.2", - "@next/swc-linux-x64-musl": "15.3.2", - "@next/swc-win32-arm64-msvc": "15.3.2", - "@next/swc-win32-x64-msvc": "15.3.2", + "@next/swc-darwin-arm64": "15.3.4", + "@next/swc-darwin-x64": "15.3.4", + "@next/swc-linux-arm64-gnu": "15.3.4", + "@next/swc-linux-arm64-musl": "15.3.4", + "@next/swc-linux-x64-gnu": "15.3.4", + "@next/swc-linux-x64-musl": "15.3.4", + "@next/swc-win32-arm64-msvc": "15.3.4", + "@next/swc-win32-x64-msvc": "15.3.4", "sharp": "^0.34.1" }, "peerDependencies": { diff --git a/package.json b/package.json index 0450d4d..5935ad9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "peas", - "version": "1.0", + "version": "nonerelease", "license": "AGPL-3.0-later", "author": { "name": "Last2014", @@ -15,6 +15,7 @@ "lint": "next lint" }, "dependencies": { + "@iconify/react": "^6.0.0", "@types/dotenv": "^6.1.1", "@types/eslint": "^9.6.1", "@types/js-cookie": "^3.0.6", @@ -25,7 +26,7 @@ "dotenv": "^16.5.0", "js-cookie": "^3.0.5", "mysql2": "^3.14.1", - "next": "^15.3.2", + "next": "^15.3.4", "nodemailer": "^7.0.3", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/src/app/api/mailverified/check/route.ts b/src/app/api/mailverified/check/route.ts index ca58f74..9ac6700 100644 --- a/src/app/api/mailverified/check/route.ts +++ b/src/app/api/mailverified/check/route.ts @@ -1,27 +1,33 @@ -import pool from '@/lib/database'; -import type { RowDataPacket } from 'mysql2'; +import pool from "@/lib/database"; +import type { RowDataPacket } from "mysql2"; -import { NextResponse, NextRequest } from 'next/server'; +import { NextResponse, NextRequest } from "next/server"; export async function POST(request: NextRequest) { - const body = await request.json(); - const { code } = body; + const body = await request.json(); + const { code } = body; - const [rows] = await pool.query( - 'SELECT * FROM mailverifiedcode WHERE code = ?', - [code] + const [rows] = await pool.query( + "SELECT * FROM mailverifiedcode WHERE code = ?", + [code], + ); + + if (rows.length === 0) { + return NextResponse.json( + { status: "error", message: "Code not found" }, + { status: 401 }, ); + } else { + if (rows[0].code === code) { + await pool.query( + "UPDATE `users` SET `mailverified` = 1 WHERE `users`.`id` = ?", + [rows[0].user], + ); - if (rows.length === 0) { - return NextResponse.json({status: "error", message: 'Code not found' }, { status: 401 }); - }else{ - if(rows[0].code === code){ - await pool.query( - 'UPDATE `users` SET `mailverified` = 1 WHERE `users`.`id` = ?', - [rows[0].user] - ); - - return NextResponse.json({status: "success", message: 'Code verified' }, { status: 200 }); - } + return NextResponse.json( + { status: "success", message: "Code verified" }, + { status: 200 }, + ); } -} \ No newline at end of file + } +} diff --git a/src/app/api/mailverified/send/route.ts b/src/app/api/mailverified/send/route.ts index c6c7973..9f4e8dd 100644 --- a/src/app/api/mailverified/send/route.ts +++ b/src/app/api/mailverified/send/route.ts @@ -1,71 +1,78 @@ -import pool from '@/lib/database'; -import type { RowDataPacket } from 'mysql2'; +import pool from "@/lib/database"; +import type { RowDataPacket } from "mysql2"; -import { sendMail } from '@/lib/mailsend'; +import { sendMail } from "@/lib/mailsend"; +import PeasConfig from "@/../peas.config"; -import { NextResponse, NextRequest } from 'next/server'; +import { NextResponse, NextRequest } from "next/server"; // コード生成 async function createCode(maxAttempts = 10) { - for (let i = 0; i < maxAttempts; i++) { - const code = Math.floor(100000 + Math.random() * 900000).toString(); - const [existingCodes] = await pool.execute( - "SELECT * FROM mailverifiedcode WHERE code = ?",[code] - ); - if (existingCodes.length === 0) { - return code; - } + for (let i = 0; i < maxAttempts; i++) { + const code = Math.floor(100000 + Math.random() * 900000).toString(); + const [existingCodes] = await pool.execute( + "SELECT * FROM mailverifiedcode WHERE code = ?", + [code], + ); + if (existingCodes.length === 0) { + return code; } - throw new Error("Failed to generate unique code after maximum attempts"); + } + throw new Error("Failed to generate unique code after maximum attempts"); } export async function POST(request: NextRequest) { - // body取得 - const body = await request.json(); - const { email, user } = body; + // body取得 + const body = await request.json(); + const { email, user } = body; - const code = await createCode(); + const code = await createCode(); - // コードが存在する場合 - const [existingCodes] = await pool.execute( - "SELECT * FROM mailverifiedcode WHERE user = ?",[user] + // コードが存在する場合 + const [existingCodes] = await pool.execute( + "SELECT * FROM mailverifiedcode WHERE user = ?", + [user], + ); + + let row: RowDataPacket[] = []; + + if (existingCodes.length !== 0) { + [row] = await pool.execute( + "UPDATE `mailverifiedcode` SET `code` = ?, `time` = current_timestamp(3) WHERE `mailverifiedcode`.`user` = ?", + [code, user], ); - - let row: RowDataPacket[] = []; + } else { + // コードが存在しない場合 + [row] = await pool.execute( + "INSERT INTO `mailverifiedcode` (`code`, `user`, `email`, `time`, `num`) VALUES (?, ?, ?, current_timestamp(3), NULL);", + [code, user, email], + ); + } - if(existingCodes.length !== 0){ - [row] = await pool.execute( - "UPDATE `mailverifiedcode` SET `code` = ?, `time` = current_timestamp(3) WHERE `mailverifiedcode`.`user` = ?", - [code, user] - ) - }else{ - // コードが存在しない場合 - [row] = await pool.execute( - "INSERT INTO `mailverifiedcode` (`code`, `user`, `email`, `time`, `num`) VALUES (?, ?, ?, current_timestamp(3), NULL);", - [code, user, email] - ) - } + // メール送信 + if ((row as RowDataPacket).affectedRows === 1) { + await sendMail({ + to: email, + subject: `【${PeasConfig.serverName}】メール認証`, + text: ` + ${user}さんこんにちは。\n + ${PeasConfig.serverName}ではメール認証が必要となっています。\n + メール認証をすることで${PeasConfig.serverName}の機能を利用することができます。\n + ${request.nextUrl.origin}/mailverify?code=${code} にアクセスしてください。`, + }); - // メール送信 - if ((row as RowDataPacket).affectedRows === 1) { - await sendMail({ - to: email, - subject: "【Peas】メール認証", - text: `${user}さんこんにちは。\n - Peasではメール認証が必要となっています。\n - メール認証をすることでPeasの機能を利用することができます。\n - ${request.nextUrl.origin}/mailverify?code=${code} にアクセスしてください。` - }) - - return NextResponse.json({ - status: "success", - code: code, - }) - }else{ - // コード保存失敗 - return NextResponse.json({ - status: "error", - error: "Failed to Save Code" - }, { status: 400 }); - } -} \ No newline at end of file + return NextResponse.json({ + status: "success", + code: code, + }); + } else { + // コード保存失敗 + return NextResponse.json( + { + status: "error", + error: "Failed to Save Code", + }, + { status: 400 }, + ); + } +} diff --git a/src/app/api/signin/route.ts b/src/app/api/signin/route.ts index e343191..a9173e4 100644 --- a/src/app/api/signin/route.ts +++ b/src/app/api/signin/route.ts @@ -1,47 +1,57 @@ -import pool from '@/lib/database'; -import type { RowDataPacket } from 'mysql2'; +import pool from "@/lib/database"; +import type { RowDataPacket } from "mysql2"; -import bcrypt from 'bcrypt'; +import bcrypt from "bcrypt"; -import { NextResponse, NextRequest } from 'next/server'; -import { cookies } from 'next/headers' +import { NextResponse, NextRequest } from "next/server"; +import { cookies } from "next/headers"; export async function POST(request: NextRequest) { - // body擾 - const body = await request.json(); - const { email, password } = body; + // body取得 + const body = await request.json(); + const { email, password } = body; - // [U[擾 - const [existingUsers] = await pool.execute( - "SELECT * FROM users WHERE email = ?", [email] + // ユーザー取得 + const [existingUsers] = await pool.execute( + "SELECT * FROM users WHERE email = ?", + [email], + ); + + // ユーザーが存在しない場合 + if (existingUsers.length === 0) { + return NextResponse.json( + { + status: "error", + error: "User not found", + }, + { status: 404 }, ); + } - // [U[݂Ȃꍇ - if (existingUsers.length === 0) { - return NextResponse.json({ - status: "error", - error: "User not found" - }, { status: 404 }); - } + const user = existingUsers[0]; + const passwordMatch = await bcrypt.compare(password, user.password); - const user = existingUsers[0]; - const passwordMatch = await bcrypt.compare(password, user.password); + // パスワード確認 + if (!passwordMatch) { + return NextResponse.json( + { + status: "error", + error: "Incorrect password", + }, + { status: 401 }, + ); + } else { + // 成功 + const sessionCookie = await cookies(); + sessionCookie.set("user", user.id); + sessionCookie.set("password", password); - // pX[hmF - if (!passwordMatch) { - return NextResponse.json({ - status: "error", - error: "Incorrect password" - }, { status: 401 }); - } else { - // - const sessionCookie = await cookies(); - sessionCookie.set('user', user.id); - sessionCookie.set('password', password); - - return NextResponse.json({ - status: "success", - message: "Login successful" - }, { status: 200 }); - } -} \ No newline at end of file + return NextResponse.json( + { + status: "success", + message: "Login successful", + }, + { status: 200 }, + ); + } +} diff --git a/src/app/api/signup/route.ts b/src/app/api/signup/route.ts index 77a3a6a..842f1fb 100644 --- a/src/app/api/signup/route.ts +++ b/src/app/api/signup/route.ts @@ -1,87 +1,114 @@ -import pool from '@/lib/database'; -import type { RowDataPacket } from 'mysql2'; +import pool from "@/lib/database"; +import type { RowDataPacket } from "mysql2"; -import bcrypt from 'bcrypt'; +import bcrypt from "bcrypt"; -import { NextResponse, NextRequest } from 'next/server'; +import { NextResponse, NextRequest } from "next/server"; export async function POST(request: NextRequest) { - const body = await request.json(); - const { email, password, id, rule } = body; + const body = await request.json(); + const { email, password, id, rule } = body; - try{ - if(!rule || rule === false){ - return NextResponse.json({ - status: "error", - error: "Please read and accept the terms and conditions" - }, { status: 400 }); - } - }catch(e){ - console.log(e); + try { + if (!rule || rule === false) { + return NextResponse.json( + { + status: "error", + error: "Please read and accept the terms and conditions", + }, + { status: 400 }, + ); } + } catch (e) { + console.log(e); + } - // bodyが空になっていないか - if(!email || !password || !id || !rule){ - return NextResponse.json({ - status: "error", - error: "Body Required" - }, { status: 400 }); - } + // bodyが空になっていないか + if (!email || !password || !id || !rule) { + return NextResponse.json( + { + status: "error", + error: "Body Required", + }, + { status: 400 }, + ); + } - // パスワードが八文字以上か - if(password.length < 8){ - return NextResponse.json({ - status: "error", - error: "Password must be at least 8 characters long" - }, { status: 400 }); - } + // パスワードが8文字以上か + if (password.length < 8) { + return NextResponse.json( + { + status: "error", + error: "Password must be at least 8 characters long", + }, + { status: 400 }, + ); + } - // ユーザー名が英数字3文字以上か - if(!/^[a-zA-Z0-9]{3,}$/.test(id)){ - return NextResponse.json({ - status: "error", - error: "Username must be at least 3 alphanumeric characters" - }, { status: 400 }); - } + // ユーザー名が英数字3文字以上か + if (!/^[a-zA-Z0-9]{3,}$/.test(id)) { + return NextResponse.json( + { + status: "error", + error: "Username must be at least 3 alphanumeric characters", + }, + { status: 400 }, + ); + } - const [existingUsers] = await pool.execute( - "SELECT * FROM users WHERE id = ?",[id] + const [existingUsers] = await pool.execute( + "SELECT * FROM users WHERE id = ?", + [id], + ); + + if (existingUsers.length === 0) { + const passwordHash = await bcrypt.hash(password, 10); + + const [result] = await pool.execute( + "INSERT INTO `users` (`id`, `password`, `email`, `mailverified`, `time`, `num`) VALUES (?, ?, ?, '0', current_timestamp(3), NULL)", + [id, passwordHash, email], ); - if(existingUsers.length === 0){ - const passwordHash = await bcrypt.hash(password, 10); + if ((result as RowDataPacket).affectedRows === 1) { + await fetch( + request.nextUrl.protocol + + request.nextUrl.host + + "/api/mailverified/send", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + email: email, + user: id, + }), + }, + ); - const [result] = await pool.execute( - "INSERT INTO `users` (`id`, `password`, `email`, `mailverified`, `time`, `num`) VALUES (?, ?, ?, '0', current_timestamp(3), NULL)", - [id, passwordHash, email] - ); - - if ((result as RowDataPacket).affectedRows === 1) { - await fetch(request.nextUrl.protocol + request.nextUrl.host + "/api/mailverified/send", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - email: email, - user: id, - }) - }); - - return NextResponse.json({ - status: "success", - message: "User created successfully" - }, { status: 201 }); - } else { - return NextResponse.json({ - status: "error", - error: "Failed to create user" - }, { status: 500 }); - } - }else{ - return NextResponse.json({ - status: "error", - error: "User already exists" - }, { status: 400 }); + return NextResponse.json( + { + status: "success", + message: "User created successfully", + }, + { status: 201 }, + ); + } else { + return NextResponse.json( + { + status: "error", + error: "Failed to create user", + }, + { status: 500 }, + ); } + } else { + return NextResponse.json( + { + status: "error", + error: "User already exists", + }, + { status: 400 }, + ); + } } diff --git a/src/app/bulma.css b/src/app/bulma.css index b799ee7..6332d7f 100644 --- a/src/app/bulma.css +++ b/src/app/bulma.css @@ -1 +1 @@ -@import 'bulma/css/bulma.css'; \ No newline at end of file +@import "bulma/css/bulma.min.css"; diff --git a/src/app/favicon.ico b/src/app/favicon.ico index 718d6fea4835ec2d246af9800eddb7ffb276240c..f4c98e2870d08ebf43e4920b450a8e6ce1833045 100644 GIT binary patch literal 15406 zcmeHN`EwLS6dp_e05(8&XLdKc>}Hc-LJ|TbghL1f2mwM4q<~UN3OS@o3oSr?Ku~Te z&T)0^3l4ZE@o2~4WGdZu?e?|ZMm ze%<|EOVTsavr>Zw5^AS3{5eTVl_bgO^uEWLBxxDil9MZckCCJ=8%t6gzCjoC@V29> z@$W?{rYE)Zxm!r>e;7udb;;ydVj)j?Q_48?W>EU}xHOewHzbj*)I^RwCbf1hF_CMf zjdE|!t@VF&K0S(>ER%`9x0Rd7xzAh2uJ=%{D^qIi+vUtyU~Npc57B46iJ}&p$iB=( z2^Fm<@8+CZ`*y!Lk(^6qiYi9GT`1d8@Z9QP7WKKisMfyBfw+n^vTcFfGRQ1J-=z_- z-@pg<4?JqDbQbiwU2?j{&M?C z8@t?_^QiTaS1E3DTXHOOk-gYP?y>}Gaj-A-zFzy<@G!Q>zqJ4|`azyT)`cdu@Y<#= zjuHoDo*fr!7->VQu4QZqs707t54&eo|O46x=DK$s3DYNe=3e(D!Ju*MfY zwC>q|P9x{?Xg|BM{&vW+Zmz;${m9$qENMs`ej4=9a@j7uuFmwywqk4*?0=NC*pIO8 zz+eLgWu>?9vwNk2b$@hNWanO=ORiP%svJAU+PRgLmhrQh8$zhS5hxrv< zp5n7Pb4`pf*HJs6yM}Acwzb4waCTT^v)$vjb@9_Ls@hm9>#Fo`^;x%|_L9NyoWIFD zGmex6CX!cUTsNX#;oY;UW4+2d*33O;3HGALUJjei0$+SZ2FYvu?JY0IJNPDwYlQ`S z+2GC%;{OIy7u;Dy%0dfSvFFx~H}hV|j#=vpc`qD{Z2l(m>;&SmmRVkN2qeF%(qS8*lu6}*Ldt(eA4-m@5R#&4fNTW&q-GB(6JMEP%^O-eqS0)wwyFho@VhoJhIA1&s90(K_cv{*yPJ$4v^a_d*Co$&y(=g`^ z9Sxov956WWC^(RRdm+xn)2YMpk(98r7d2UxLe}DF#ELxRDr-e8KJQQ6et#G7xcPNz zZ_jbXPQQ$$q`f)Rq_inwsSVW}ojC7v&9Npm2DR8u&30u|)`iKsoJSutPCc*8AkW55 z$kQn*W@WyKB4^{wj#`Uf`^@KR=gN4*Wkbv7JqSxEzvteVN9o@TA*C?NYg=H7gpI9R zy>Sm4YJRt)y?UHajE8M;EciA}S1xf3qU*W$)cBme$gE;xeDYFnOi&xM;kY=rJJ{Zu z*p=_Oq%pNQI!rhGjJ@r#*C`6IdzAx~m^h!!F20qM{ecYv1>U46% zBaa6OPURublK$-w#Qf#^_@Lt7;E!GCz;Qb}^wZ*xDR-$coj&&-J~l7#)mHbsJcAN< z_k>?*bRTxc#bco$$6llT(XJhnz=5x#|MGsnDfH^h$E zuMFNt&iDTAS{_L)4s?FtU60y05bAF^*Jfi4&GzLO*oRo)OYA$mHfFA{k7RwsIB?Bt z6vytl=66q-gW4S(fOwjIcPR29D`{_UaHf$Dn`Pep{*iUy*s_{SsPrpZUD(I)eU)JD z$%Ja(K;N^S(!L&yJYp4g#$@JP5909E3v*n7Zd%-&7m3>#N7)zO(T(e@59brr+5GT&*c=_CF5(z&O$$qgCBlFyXcJ?ne zdjQ7EIT^llAneXQCga3# z-Sz@n+~PTDUthAYtu;Af!F81ql;J$C;X2)AgD+nni#-?hSwv-N9_bv+f>f~k`a^z-cnV%EB;>)BCt z-sW1pI{Z8u=d1`y+@6X(`}lf=o%P`JR{ZuX%+n9*tdG&!+jute4+aMe4j3FTI8Yxr Pfa^iVpJ9RjZGry)E7(>J literal 25931 zcmeHv30#a{`}aL_*G&7qml|y<+KVaDM2m#dVr!KsA!#An?kSQM(q<_dDNCpjEux83 zLb9Z^XxbDl(w>%i@8hT6>)&Gu{h#Oeyszu?xtw#Zb1mO{pgX9699l+Qppw7jXaYf~-84xW z)w4x8?=youko|}Vr~(D$UXIbiXABHh`p1?nn8Po~fxRJv}|0e(BPs|G`(TT%kKVJAdg5*Z|x0leQq0 zkdUBvb#>9F()jo|T~kx@OM8$9wzs~t2l;K=woNssA3l6|sx2r3+kdfVW@e^8e*E}v zA1y5{bRi+3Z`uD3{F7LgFJDdvm;nJilkzDku>BwXH(8ItVCXk*-lSJnR?-2UN%hJ){&rlvg`CDTj z)Bzo!3v7Ou#83zEDEFcKt(f1E0~=rqeEbTnMvWR#{+9pg%7G8y>u1OVRUSoox-ovF z2Ydma(;=YuBY(eI|04{hXzZD6_f(v~H;C~y5=DhAC{MMS>2fm~1H_t2$56pc$NH8( z5bH|<)71dV-_oCHIrzrT`2s-5w_+2CM0$95I6X8p^r!gHp+j_gd;9O<1~CEQQGS8) zS9Qh3#p&JM-G8rHekNmKVewU;pJRcTAog68KYo^dRo}(M>36U4Us zfgYWSiHZL3;lpWT=zNAW>Dh#mB!_@Lg%$ms8N-;aPqMn+C2HqZgz&9~Eu z4|Kp<`$q)Uw1R?y(~S>ePdonHxpV1#eSP1B;Ogo+-Pk}6#0GsZZ5!||ev2MGdh}_m z{DeR7?0-1^zVs&`AV6Vt;r3`I`OI_wgs*w=eO%_#7Kepl{B@xiyCANc(l zzIyd4y|c6PXWq9-|KM8(zIk8LPk(>a)zyFWjhT!$HJ$qX1vo@d25W<fvZQ2zUz5WRc(UnFMKHwe1| zWmlB1qdbiA(C0jmnV<}GfbKtmcu^2*P^O?MBLZKt|As~ge8&AAO~2K@zbXelK|4T<{|y4`raF{=72kC2Kn(L4YyenWgrPiv z@^mr$t{#X5VuIMeL!7Ab6_kG$&#&5p*Z{+?5U|TZ`B!7llpVmp@skYz&n^8QfPJzL z0G6K_OJM9x+Wu2gfN45phANGt{7=C>i34CV{Xqlx(fWpeAoj^N0Biu`w+MVcCUyU* zDZuzO0>4Z6fbu^T_arWW5n!E45vX8N=bxTVeFoep_G#VmNlQzAI_KTIc{6>c+04vr zx@W}zE5JNSU>!THJ{J=cqjz+4{L4A{Ob9$ZJ*S1?Ggg3klFp!+Y1@K+pK1DqI|_gq z5ZDXVpge8-cs!o|;K73#YXZ3AShj50wBvuq3NTOZ`M&qtjj#GOFfgExjg8Gn8>Vq5 z`85n+9|!iLCZF5$HJ$Iu($dm?8~-ofu}tEc+-pyke=3!im#6pk_Wo8IA|fJwD&~~F zc16osQ)EBo58U7XDuMexaPRjU@h8tXe%S{fA0NH3vGJFhuyyO!Uyl2^&EOpX{9As0 zWj+P>{@}jxH)8|r;2HdupP!vie{sJ28b&bo!8`D^x}TE$%zXNb^X1p@0PJ86`dZyj z%ce7*{^oo+6%&~I!8hQy-vQ7E)0t0ybH4l%KltWOo~8cO`T=157JqL(oq_rC%ea&4 z2NcTJe-HgFjNg-gZ$6!Y`SMHrlj}Etf7?r!zQTPPSv}{so2e>Fjs1{gzk~LGeesX%r(Lh6rbhSo_n)@@G-FTQy93;l#E)hgP@d_SGvyCp0~o(Y;Ee8{ zdVUDbHm5`2taPUOY^MAGOw*>=s7=Gst=D+p+2yON!0%Hk` zz5mAhyT4lS*T3LS^WSxUy86q&GnoHxzQ6vm8)VS}_zuqG?+3td68_x;etQAdu@sc6 zQJ&5|4(I?~3d-QOAODHpZ=hlSg(lBZ!JZWCtHHSj`0Wh93-Uk)_S%zsJ~aD>{`A0~ z9{AG(e|q3g5B%wYKRxiL2Y$8(4w6bzchKuloQW#e&S3n+P- z8!ds-%f;TJ1>)v)##>gd{PdS2Oc3VaR`fr=`O8QIO(6(N!A?pr5C#6fc~Ge@N%Vvu zaoAX2&(a6eWy_q&UwOhU)|P3J0Qc%OdhzW=F4D|pt0E4osw;%<%Dn58hAWD^XnZD= z>9~H(3bmLtxpF?a7su6J7M*x1By7YSUbxGi)Ot0P77`}P3{)&5Un{KD?`-e?r21!4vTTnN(4Y6Lin?UkSM z`MXCTC1@4A4~mvz%Rh2&EwY))LeoT=*`tMoqcEXI>TZU9WTP#l?uFv+@Dn~b(>xh2 z;>B?;Tz2SR&KVb>vGiBSB`@U7VIWFSo=LDSb9F{GF^DbmWAfpms8Sx9OX4CnBJca3 zlj9(x!dIjN?OG1X4l*imJNvRCk}F%!?SOfiOq5y^mZW)jFL@a|r-@d#f7 z2gmU8L3IZq0ynIws=}~m^#@&C%J6QFo~Mo4V`>v7MI-_!EBMMtb%_M&kvAaN)@ZVw z+`toz&WG#HkWDjnZE!6nk{e-oFdL^$YnbOCN}JC&{$#$O27@|Tn-skXr)2ml2~O!5 zX+gYoxhoc7qoU?C^3~&!U?kRFtnSEecWuH0B0OvLodgUAi}8p1 zrO6RSXHH}DMc$&|?D004DiOVMHV8kXCP@7NKB zgaZq^^O<7PoKEp72kby@W0Z!Y*Ay{&vfg#C&gG@YVR9g?FEocMUi1gSN$+V+ayF45{a zuDZDTN}mS|;BO%gEf}pjBfN2-gIrU#G5~cucA;dokXW89%>AyXJJI z9X4UlIWA|ZYHgbI z5?oFk@A=Ik7lrEQPDH!H+b`7_Y~aDb_qa=B2^Y&Ow41cU=4WDd40dp5(QS-WMN-=Y z9g;6_-JdNU;|6cPwf$ak*aJIcwL@1n$#l~zi{c{EW?T;DaW*E8DYq?Umtz{nJ&w-M zEMyTDrC&9K$d|kZe2#ws6)L=7K+{ zQw{XnV6UC$6-rW0emqm8wJoeZK)wJIcV?dST}Z;G0Arq{dVDu0&4kd%N!3F1*;*pW zR&qUiFzK=@44#QGw7k1`3t_d8&*kBV->O##t|tonFc2YWrL7_eqg+=+k;!F-`^b8> z#KWCE8%u4k@EprxqiV$VmmtiWxDLgnGu$Vs<8rppV5EajBXL4nyyZM$SWVm!wnCj-B!Wjqj5-5dNXukI2$$|Bu3Lrw}z65Lc=1G z^-#WuQOj$hwNGG?*CM_TO8Bg-1+qc>J7k5c51U8g?ZU5n?HYor;~JIjoWH-G>AoUP ztrWWLbRNqIjW#RT*WqZgPJXU7C)VaW5}MiijYbABmzoru6EmQ*N8cVK7a3|aOB#O& zBl8JY2WKfmj;h#Q!pN%9o@VNLv{OUL?rixHwOZuvX7{IJ{(EdPpuVFoQqIOa7giLVkBOKL@^smUA!tZ1CKRK}#SSM)iQHk)*R~?M!qkCruaS!#oIL1c z?J;U~&FfH#*98^G?i}pA{ z9Jg36t4=%6mhY(quYq*vSxptes9qy|7xSlH?G=S@>u>Ebe;|LVhs~@+06N<4CViBk zUiY$thvX;>Tby6z9Y1edAMQaiH zm^r3v#$Q#2T=X>bsY#D%s!bhs^M9PMAcHbCc0FMHV{u-dwlL;a1eJ63v5U*?Q_8JO zT#50!RD619#j_Uf))0ooADz~*9&lN!bBDRUgE>Vud-i5ck%vT=r^yD*^?Mp@Q^v+V zG#-?gKlr}Eeqifb{|So?HM&g91P8|av8hQoCmQXkd?7wIJwb z_^v8bbg`SAn{I*4bH$u(RZ6*xUhuA~hc=8czK8SHEKTzSxgbwi~9(OqJB&gwb^l4+m`k*Q;_?>Y-APi1{k zAHQ)P)G)f|AyjSgcCFps)Fh6Bca*Xznq36!pV6Az&m{O8$wGFD? zY&O*3*J0;_EqM#jh6^gMQKpXV?#1?>$ml1xvh8nSN>-?H=V;nJIwB07YX$e6vLxH( zqYwQ>qxwR(i4f)DLd)-$P>T-no_c!LsN@)8`e;W@)-Hj0>nJ-}Kla4-ZdPJzI&Mce zv)V_j;(3ERN3_@I$N<^|4Lf`B;8n+bX@bHbcZTopEmDI*Jfl)-pFDvo6svPRoo@(x z);_{lY<;);XzT`dBFpRmGrr}z5u1=pC^S-{ce6iXQlLGcItwJ^mZx{m$&DA_oEZ)B{_bYPq-HA zcH8WGoBG(aBU_j)vEy+_71T34@4dmSg!|M8Vf92Zj6WH7Q7t#OHQqWgFE3ARt+%!T z?oLovLVlnf?2c7pTc)~cc^($_8nyKwsN`RA-23ed3sdj(ys%pjjM+9JrctL;dy8a( z@en&CQmnV(()bu|Y%G1-4a(6x{aLytn$T-;(&{QIJB9vMox11U-1HpD@d(QkaJdEb zG{)+6Dos_L+O3NpWo^=gR?evp|CqEG?L&Ut#D*KLaRFOgOEK(Kq1@!EGcTfo+%A&I z=dLbB+d$u{sh?u)xP{PF8L%;YPPW53+@{>5W=Jt#wQpN;0_HYdw1{ksf_XhO4#2F= zyPx6Lx2<92L-;L5PD`zn6zwIH`Jk($?Qw({erA$^bC;q33hv!d!>%wRhj# zal^hk+WGNg;rJtb-EB(?czvOM=H7dl=vblBwAv>}%1@{}mnpUznfq1cE^sgsL0*4I zJ##!*B?=vI_OEVis5o+_IwMIRrpQyT_Sq~ZU%oY7c5JMIADzpD!Upz9h@iWg_>>~j zOLS;wp^i$-E?4<_cp?RiS%Rd?i;f*mOz=~(&3lo<=@(nR!_Rqiprh@weZlL!t#NCc zO!QTcInq|%#>OVgobj{~ixEUec`E25zJ~*DofsQdzIa@5^nOXj2T;8O`l--(QyU^$t?TGY^7#&FQ+2SS3B#qK*k3`ye?8jUYSajE5iBbJls75CCc(m3dk{t?- zopcER9{Z?TC)mk~gpi^kbbu>b-+a{m#8-y2^p$ka4n60w;Sc2}HMf<8JUvhCL0B&Btk)T`ctE$*qNW8L$`7!r^9T+>=<=2qaq-;ll2{`{Rg zc5a0ZUI$oG&j-qVOuKa=*v4aY#IsoM+1|c4Z)<}lEDvy;5huB@1RJPquU2U*U-;gu z=En2m+qjBzR#DEJDO`WU)hdd{Vj%^0V*KoyZ|5lzV87&g_j~NCjwv0uQVqXOb*QrQ zy|Qn`hxx(58c70$E;L(X0uZZ72M1!6oeg)(cdKO ze0gDaTz+ohR-#d)NbAH4x{I(21yjwvBQfmpLu$)|m{XolbgF!pmsqJ#D}(ylp6uC> z{bqtcI#hT#HW=wl7>p!38sKsJ`r8}lt-q%Keqy%u(xk=yiIJiUw6|5IvkS+#?JTBl z8H5(Q?l#wzazujH!8o>1xtn8#_w+397*_cy8!pQGP%K(Ga3pAjsaTbbXJlQF_+m+-UpUUent@xM zg%jqLUExj~o^vQ3Gl*>wh=_gOr2*|U64_iXb+-111aH}$TjeajM+I20xw(((>fej-@CIz4S1pi$(#}P7`4({6QS2CaQS4NPENDp>sAqD z$bH4KGzXGffkJ7R>V>)>tC)uax{UsN*dbeNC*v}#8Y#OWYwL4t$ePR?VTyIs!wea+ z5Urmc)X|^`MG~*dS6pGSbU+gPJoq*^a=_>$n4|P^w$sMBBy@f*Z^Jg6?n5?oId6f{ z$LW4M|4m502z0t7g<#Bx%X;9<=)smFolV&(V^(7Cv2-sxbxopQ!)*#ZRhTBpx1)Fc zNm1T%bONzv6@#|dz(w02AH8OXe>kQ#1FMCzO}2J_mST)+ExmBr9cva-@?;wnmWMOk z{3_~EX_xadgJGv&H@zK_8{(x84`}+c?oSBX*Ge3VdfTt&F}yCpFP?CpW+BE^cWY0^ zb&uBN!Ja3UzYHK-CTyA5=L zEMW{l3Usky#ly=7px648W31UNV@K)&Ub&zP1c7%)`{);I4b0Q<)B}3;NMG2JH=X$U zfIW4)4n9ZM`-yRj67I)YSLDK)qfUJ_ij}a#aZN~9EXrh8eZY2&=uY%2N0UFF7<~%M zsB8=erOWZ>Ct_#^tHZ|*q`H;A)5;ycw*IcmVxi8_0Xk}aJA^ath+E;xg!x+As(M#0=)3!NJR6H&9+zd#iP(m0PIW8$ z1Y^VX`>jm`W!=WpF*{ioM?C9`yOR>@0q=u7o>BP-eSHqCgMDj!2anwH?s%i2p+Q7D zzszIf5XJpE)IG4;d_(La-xenmF(tgAxK`Y4sQ}BSJEPs6N_U2vI{8=0C_F?@7<(G; zo$~G=8p+076G;`}>{MQ>t>7cm=zGtfbdDXm6||jUU|?X?CaE?(<6bKDYKeHlz}DA8 zXT={X=yp_R;HfJ9h%?eWvQ!dRgz&Su*JfNt!Wu>|XfU&68iRikRrHRW|ZxzRR^`eIGt zIeiDgVS>IeExKVRWW8-=A=yA`}`)ZkWBrZD`hpWIxBGkh&f#ijr449~m`j6{4jiJ*C!oVA8ZC?$1RM#K(_b zL9TW)kN*Y4%^-qPpMP7d4)o?Nk#>aoYHT(*g)qmRUb?**F@pnNiy6Fv9rEiUqD(^O zzyS?nBrX63BTRYduaG(0VVG2yJRe%o&rVrLjbxTaAFTd8s;<<@Qs>u(<193R8>}2_ zuwp{7;H2a*X7_jryzriZXMg?bTuegABb^87@SsKkr2)0Gyiax8KQWstw^v#ix45EVrcEhr>!NMhprl$InQMzjSFH54x5k9qHc`@9uKQzvL4ihcq{^B zPrVR=o_ic%Y>6&rMN)hTZsI7I<3&`#(nl+3y3ys9A~&^=4?PL&nd8)`OfG#n zwAMN$1&>K++c{^|7<4P=2y(B{jJsQ0a#U;HTo4ZmWZYvI{+s;Td{Yzem%0*k#)vjpB zia;J&>}ICate44SFYY3vEelqStQWFihx%^vQ@Do(sOy7yR2@WNv7Y9I^yL=nZr3mb zXKV5t@=?-Sk|b{XMhA7ZGB@2hqsx}4xwCW!in#C zI@}scZlr3-NFJ@NFaJlhyfcw{k^vvtGl`N9xSo**rDW4S}i zM9{fMPWo%4wYDG~BZ18BD+}h|GQKc-g^{++3MY>}W_uq7jGHx{mwE9fZiPCoxN$+7 zrODGGJrOkcPQUB(FD5aoS4g~7#6NR^ma7-!>mHuJfY5kTe6PpNNKC9GGRiu^L31uG z$7v`*JknQHsYB!Tm_W{a32TM099djW%5e+j0Ve_ct}IM>XLF1Ap+YvcrLV=|CKo6S zb+9Nl3_YdKP6%Cxy@6TxZ>;4&nTneadr z_ES90ydCev)LV!dN=#(*f}|ZORFdvkYBni^aLbUk>BajeWIOcmHP#8S)*2U~QKI%S zyrLmtPqb&TphJ;>yAxri#;{uyk`JJqODDw%(Z=2`1uc}br^V%>j!gS)D*q*f_-qf8&D;W1dJgQMlaH5er zN2U<%Smb7==vE}dDI8K7cKz!vs^73o9f>2sgiTzWcwY|BMYHH5%Vn7#kiw&eItCqa zIkR2~Q}>X=Ar8W|^Ms41Fm8o6IB2_j60eOeBB1Br!boW7JnoeX6Gs)?7rW0^5psc- zjS16yb>dFn>KPOF;imD}e!enuIniFzv}n$m2#gCCv4jM#ArwlzZ$7@9&XkFxZ4n!V zj3dyiwW4Ki2QG{@i>yuZXQizw_OkZI^-3otXC{!(lUpJF33gI60ak;Uqitp74|B6I zgg{b=Iz}WkhCGj1M=hu4#Aw173YxIVbISaoc z-nLZC*6Tgivd5V`K%GxhBsp@SUU60-rfc$=wb>zdJzXS&-5(NRRodFk;Kxk!S(O(a0e7oY=E( zAyS;Ow?6Q&XA+cnkCb{28_1N8H#?J!*$MmIwLq^*T_9-z^&UE@A(z9oGYtFy6EZef LrJugUA?W`A8`#=m diff --git a/src/app/layout.tsx b/src/app/layout.tsx index deefaf1..81b3e64 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -2,9 +2,11 @@ import type { Metadata } from "next"; import "./bulma.css"; import "./global.css"; +import PeasConfig from "@/../peas.config"; + export const metadata: Metadata = { - title: "Peas", - description: "Peas SNS", + title: PeasConfig.serverName, + description: PeasConfig.serverDescription, }; export default function RootLayout({ @@ -14,13 +16,7 @@ export default function RootLayout({ }>) { return ( - -
- Peas -
- - {children} - + {children} ); } diff --git a/src/app/mailverify/page.tsx b/src/app/mailverify/page.tsx index 08f05ad..f3b7d75 100644 --- a/src/app/mailverify/page.tsx +++ b/src/app/mailverify/page.tsx @@ -1,53 +1,90 @@ "use client"; +import { useState } from "react"; import { useSearchParams } from "next/navigation"; +import { Icon } from "@iconify/react"; +import { MessageLang } from "@/lang/component/client"; +import { MessageLangVar } from "@/lang/component/variable"; export default function MailVerify() { - const searchParams = useSearchParams(); - const URLCode = searchParams.get("code"); + const [loading, setLoading] = useState(false); - let code: string = ""; + const searchParams = useSearchParams(); + const URLCode = searchParams.get("code"); - if(URLCode){ - code = URLCode; + let code: string = ""; + + if (URLCode !== null) { + code = URLCode; + } + + const verify = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + + if (code === "") { + code = e.currentTarget.code.value; } - const verify = async (e: React.FormEvent) => { - if(code === ""){ - code = e.currentTarget.code.value; - } + if (code.length !== 6) { + alert(MessageLangVar({text: "codeCharacterCount"})); + return; + } - if (code.length !== 6) { - alert("コードは6桁で入力してください。"); - return; - } + try { + const res = await fetch(`${location.origin}/api/mailverified/check`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + code, + }), + }); - const res = await fetch(`${location.origin}/api/mailverify/check`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - code, - }), - }); - const data = await res.text(); - const result = JSON.parse(data); + const data = await res.text(); + const result = JSON.parse(data); - if (result.status === "success") { - alert("メール認証に成功しました。\nログインしてください。"); - window.location.href = "/login"; - } - }; + if (result.status === "success") { + alert(MessageLangVar({ text: "emailVerificationSuccess" })); + window.location.href = "/signin"; + } + } catch (err) { + console.error(err) + } finally { + setLoading(false); + } + }; - return ( - <> -

メール認証

+ return ( + <> +

-
- - -
- - ) -} \ No newline at end of file + {loading && ( +
+ +
+ )} + +
+ + +
+ +
+ + ); +} diff --git a/src/app/manifest.ts b/src/app/manifest.ts new file mode 100644 index 0000000..3362c49 --- /dev/null +++ b/src/app/manifest.ts @@ -0,0 +1,20 @@ +import type { MetadataRoute } from "next"; +import PeasConfig from "@/../peas.config"; + +export default function manifest(): MetadataRoute.Manifest { + return { + name: PeasConfig.serverName, + description: PeasConfig.serverDescription, + start_url: "/", + display: "standalone", + background_color: "#fff", + theme_color: "#fff", + icons: [ + { + src: "/favicon.ico", + sizes: "any", + type: "image/x-icon", + }, + ], + }; +} diff --git a/src/app/page.tsx b/src/app/page.tsx index f9ff706..4070f88 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,26 +1,54 @@ "use client"; -import "./style.css" +import "./style.css"; import Link from "next/link"; +import { Icon } from "@iconify/react"; +import PeasConfig from "@/../peas.config"; import { MessageLang } from "@/lang/component/client"; export default function Home() { return ( <> +

{PeasConfig.serverName}

+
-

- +

+ + +

+ +

{PeasConfig.serverDescription}

+
+ +
+

+ + +

+

-
- -

- +
+ + + +

+ +

+ + +
); diff --git a/src/app/robots.ts b/src/app/robots.ts new file mode 100644 index 0000000..f05b332 --- /dev/null +++ b/src/app/robots.ts @@ -0,0 +1,20 @@ +import type { MetadataRoute } from "next"; +import PeasConfig from "../../peas.config"; + +let protocol: string; + +if (PeasConfig.ssl.ssl) { + protocol = "https"; +} else { + protocol = "http"; +} + +export default function robots(): MetadataRoute.Robots { + return { + rules: { + userAgent: "*", + allow: "/", + }, + sitemap: `${protocol}://${PeasConfig.serverHost}/sitemap.xml`, + }; +} diff --git a/src/app/signin/page.tsx b/src/app/signin/page.tsx index c12c7ab..80bb3d0 100644 --- a/src/app/signin/page.tsx +++ b/src/app/signin/page.tsx @@ -1,46 +1,93 @@ "use client"; import "./style.css"; +import { useState } from "react"; import { MessageLang } from "@/lang/component/client"; +import { Icon } from "@iconify/react"; export default function SignIn() { - const FormSubmit = async (e: React.FormEvent) => { - e.preventDefault(); + const [error, setError] = useState(false); + const [loading, setLoading] = useState(false); - const res = await fetch("/api/signin", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - email: e.currentTarget.email.value, - password: e.currentTarget.password.value, - }), - }); - const data = await res.text(); - const json = JSON.parse(data); + const FormSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); - if (json.status === "success") { - window.location.href = "/"; - } + try { + const res = await fetch("/api/signin", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + email: e.currentTarget.email.value, + password: e.currentTarget.password.value, + }), + }); + const data = await res.text(); + const json = JSON.parse(data); + + if (json.status === "success") { + window.location.href = "/"; + } else { + setError(true); + window.location.replace("/signin?error=true"); + } + } catch (err) { + console.log(err); + setError(true); + window.location.replace("/signin?error=true"); + } finally { + setLoading(false); } + }; - return ( -
-

+ useState(() => { + const locationURL = new URL(window.location.href); + if (locationURL.searchParams.get("error")) { + setError(true); + } + }); - - + return ( + + {error && ( +
+
+ + +
+
+ )} -
+

+ +

- - + {loading && ( +
+ +
+ )} -
+ + - -
- ) -} \ No newline at end of file +
+ + + + +
+ + + + ); +} diff --git a/src/app/signin/style.css b/src/app/signin/style.css index cbd57b6..a6e9129 100644 --- a/src/app/signin/style.css +++ b/src/app/signin/style.css @@ -1,7 +1,7 @@ -.input{ +.input { width: 20rem; } -.button{ +.button { margin-top: 5px; -} \ No newline at end of file +} diff --git a/src/app/signup/page.tsx b/src/app/signup/page.tsx index 3a29e3d..9ac8f51 100644 --- a/src/app/signup/page.tsx +++ b/src/app/signup/page.tsx @@ -1,77 +1,174 @@ "use client"; import Link from "next/link"; +import { useState } from "react"; +import { Icon } from "@iconify/react"; import "./style.css"; import { MessageLang } from "@/lang/component/client"; +import { MessageLangVar } from "@/lang/component/variable"; export default function SignUp() { - const FormSubmit = async (e: React.FormEvent) => { - e.preventDefault(); + const [loading, setLoading] = useState(false); - const res = await fetch("/api/signup", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - email: e.currentTarget.email.value, - password: e.currentTarget.password.value, - id: e.currentTarget.username.value, - rule: e.currentTarget.ruleCheck.checked, - }), - }); - const data = await res.text(); - const json = JSON.parse(data); + const FormSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); - if (json.status === "success") { - alert("アカウントの作成に成功しました。\nご登録いただいたメールアドレスに確認用メールを送信しました。\n確認メールをクリックしてアカウントを有効化してください。") - window.location.href = "/"; - }else{ - if(json.error === "User already exists"){ - alert("同じユーザー名が既に使用されています") - }else if(json.error === "Please read and accept the terms and conditions"){ - alert("利用規約とプライバシーポリシーに同意してください") - }else if(json.error === "Body Required"){ - alert("Bodyは必須です") - }else if(json.error === "Password must be at least 8 characters long"){ - alert("パスワードは8文字以上にしてください") - }else if(json.error === "Username must be at least 3 alphanumeric characters"){ - alert("ユーザー名は3文字以上の英数字のIDにしてください") - }else if(json.error === "Failed to create user"){ - alert("ユーザーの作成に失敗しました") - } + try { + const res = await fetch("/api/signup", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + email: e.currentTarget.email.value, + password: e.currentTarget.password.value, + id: e.currentTarget.username.value, + rule: e.currentTarget.ruleCheck.checked, + }), + }); + + const data = await res.text(); + const json = JSON.parse(data); + + if (json.status === "success") { + alert(MessageLangVar({ text: "accountCreateSuccess" })); + window.location.href = "/"; + } else { + if (json.error === "User already exists") { + alert(MessageLangVar({ text: "sameUsername" })); + } else if ( + json.error === "Please read and accept the terms and conditions" + ) { + alert(MessageLangVar({ text: "termsAgreement" })); + } else if (json.error === "Body Required") { + alert(MessageLangVar({ text: "bodyRequired" })); + } else if ( + json.error === "Password must be at least 8 characters long" + ) { + alert(MessageLangVar({ text: "passwordCharacterLimit" })); + } else if ( + json.error === "Username must be at least 3 alphanumeric characters" + ) { + alert(MessageLangVar({ text: "usernameRestriction" })); + } else if (json.error === "Failed to create user") { + alert(MessageLangVar({ text: "userCreationFailed" })); } + } + } catch (err) { + alert(`${MessageLangVar({ text: "connectionError" })}: ${err}`); + } finally { + setLoading(false); } + }; - return ( -
-

+ return ( +
+ +

+ +

- -
- @ - -
-

+ {loading && ( +
+ +
+ )} - - -

+ +
+ + @ + + +
+

+ +

- - -

+ + +

+ +

- + + +

+ +

- + - - - ) -} \ No newline at end of file + + + + +
+ ); +} diff --git a/src/app/signup/style.css b/src/app/signup/style.css index 06ccb9b..06d8fac 100644 --- a/src/app/signup/style.css +++ b/src/app/signup/style.css @@ -1,16 +1,16 @@ -.input{ +.input { width: 20rem; } -.button{ +.button { margin-top: 5px; } -.input-out{ - position: absolute; +.input-out { + position: absolute; left: 10px; - top: 50%; - transform: translateY(-50%); + top: 50%; + transform: translateY(-50%); pointer-events: none; z-index: 1; background-color: transparent; @@ -18,6 +18,6 @@ font-weight: normal; } -.inputout-email{ +.inputout-email { z-index: 1; -} \ No newline at end of file +} diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts new file mode 100644 index 0000000..fb97d30 --- /dev/null +++ b/src/app/sitemap.ts @@ -0,0 +1,5 @@ +import type { MetadataRoute } from "next"; + +export default function sitemap(): MetadataRoute.Sitemap { + return []; +} diff --git a/src/app/style.css b/src/app/style.css index 54376a8..c3fde47 100644 --- a/src/app/style.css +++ b/src/app/style.css @@ -1,7 +1,16 @@ -.about{ - margin: 10px; +.about { + margin: 0 auto; + margin-top: 10px; + max-width: 40rem; } -.signup,.signin{ +.signup, +.signin { margin: 10px; -} \ No newline at end of file + display: inline-block; +} + +.peasAboutTitle { + position: absolute; + margin-left: 16px; +} diff --git a/src/lang/component/client.tsx b/src/lang/component/client.tsx index 71da81b..0bdbbce 100644 --- a/src/lang/component/client.tsx +++ b/src/lang/component/client.tsx @@ -1,27 +1,33 @@ -'use client'; +"use client"; +import { useEffect, useState } from "react"; +import Cookies from "js-cookie"; +import { messages } from "../messages"; +import { MessageLangProps, SupportedLanguage, isValidLanguage } from "../types"; -import { useEffect, useState } from 'react'; -import Cookies from 'js-cookie'; -import { messages } from '../messages'; -import { MessageLangProps, SupportedLanguage, isValidLanguage } from '../types'; +interface MessageLangPropsWithStyle + extends MessageLangProps, + React.HTMLAttributes {} -export function MessageLang({ text, data = [] }: MessageLangProps) { - const [lang, setLang] = useState('ja'); +export function MessageLang({ + text, + data = [], + ...htmlProps +}: MessageLangPropsWithStyle) { + const [lang, setLang] = useState("ja"); - useEffect(() => { - const browserLang = navigator.language.split('-')[0]; - const savedLang = Cookies.get('lang') || browserLang || 'ja'; - if (isValidLanguage(savedLang)) { - setLang(savedLang); - Cookies.set('lang', savedLang); - } - }, []); + useEffect(() => { + const browserLang = navigator.language.split("-")[0]; + const savedLang = Cookies.get("lang") || browserLang || "ja"; + if (isValidLanguage(savedLang)) { + setLang(savedLang); + Cookies.set("lang", savedLang); + } + }, []); - let message = messages[lang][text]; + let message = messages[lang][text]; + data.forEach((value, index) => { + message = message.replace(new RegExp("\\{" + index + "\\}", "g"), value); + }); - data.forEach((value, index) => { - message = message.replace(new RegExp('\\{' + index + '\\}', 'g'), value); - }); - - return ; -} \ No newline at end of file + return ; +} diff --git a/src/lang/component/server.tsx b/src/lang/component/server.tsx index 2b27350..427be3c 100644 --- a/src/lang/component/server.tsx +++ b/src/lang/component/server.tsx @@ -3,15 +3,15 @@ import { messages } from '../messages'; import { MessageLangProps, isValidLanguage } from '../types'; export async function MessageLang({ text, data = [] }: MessageLangProps) { - const cookieStore = await cookies(); - const savedLang = cookieStore.get('lang')?.value || 'ja'; - const lang = isValidLanguage(savedLang) ? savedLang : 'ja'; + const cookieStore = await cookies(); + const savedLang = cookieStore.get('lang')?.value || 'ja'; + const lang = isValidLanguage(savedLang) ? savedLang : 'ja'; - let message = messages[lang][text]; + let message = messages[lang][text]; - data.forEach((value, index) => { - message = message.replace(new RegExp('\\{' + index + '\\}', 'g'), value); - }); + data.forEach((value, index) => { + message = message.replace(new RegExp('\\{' + index + '\\}', 'g'), value); + }); - return ; -} \ No newline at end of file + return ; +} diff --git a/src/lang/component/variable.ts b/src/lang/component/variable.ts index a125831..ddef94b 100644 --- a/src/lang/component/variable.ts +++ b/src/lang/component/variable.ts @@ -1,20 +1,25 @@ import { messages } from '../messages'; import { MessageLangProps, SupportedLanguage } from '../types'; -export function MessageLangVar({ text, data = [], variables = {} }: MessageLangProps & { variables?: Record; }, langFunc?: string) { - const lang: SupportedLanguage = (langFunc as SupportedLanguage) || 'ja'; +export function MessageLangVar({ + text, data = [], + variables = {} +}: MessageLangProps & { + variables?: Record; +}, langFunc?: string) { + const lang: SupportedLanguage = (langFunc as SupportedLanguage) || 'ja'; - let message = messages[lang][text]; + let message = messages[lang][text]; - data.forEach((value, index) => { - message = message.replace(new RegExp('\\{' + index + '\\}', 'g'), value); + data.forEach((value, index) => { + message = message.replace(new RegExp('\\{' + index + '\\}', 'g'), value); + }); + + if (variables) { + Object.keys(variables).forEach(key => { + message = message.replace(new RegExp('\\{' + key + '\\}', 'g'), variables[key]); }); + } - if (variables) { - Object.keys(variables).forEach(key => { - message = message.replace(new RegExp('\\{' + key + '\\}', 'g'), variables[key]); - }); - } - - return message; + return message; } diff --git a/src/lang/messages.ts b/src/lang/messages.ts index 031c17e..9f1207f 100644 --- a/src/lang/messages.ts +++ b/src/lang/messages.ts @@ -1,40 +1,95 @@ export const messages = { - ja: { - PeasAboutTitle: "Peasについて", - PeasAboutText: ` - Peasはプロフィール共有を目的としたSNSです。
- ユーザーのプロフィールを登録し、共有することが出来ます。
- 学校や職場で使用できるようなプロフィール欄も自由に作成でき、公開設定も変更できます。
- QRコードの印刷にも対応していて共有にも最適です。 - `, - or: "または", - Start: "今すぐ始める", - Signin: "サインイン", - Signup: "サインアップ", - email: "メールアドレス", - password: "パスワード", - username: "ユーザー名", - idHelp: "@から始まる英数字のID(3文字以上)", - mailHelp: "メールアドレスは受信できるものを使用してください", - passwordHelp: "8文字以上のパスワードを使用してください" - }, - en: { - PeasAboutTitle: "About Peas", - PeasAboutText: ` - Peas is a SNS designed for sharing profiles.
- You can register and share your user profile.
- You can freely create profile sections suitable for use at school or work, and also change the privacy settings.
- It also supports printing QR codes, making sharing easy. - `, - or: "or", - Start: "Get Started Now", - Signin: "Sign In", - Signup: "Sign Up", - email: "Email", - password: "Password", - username: "Username", - idHelp: "ID starting with @ and consisting of English numbers (at least 3 characters)", - mailHelp: "Use an email address that can be received", - passwordHelp: "Use a password of at least 8 characters" - } -}; \ No newline at end of file + ja: { + PeasAboutTitle: "Peasについて", + PeasAboutText: ` + Peasはプロフィール共有を目的としたSNSです。
+ ユーザーのプロフィールを登録し、共有することが出来ます。
+ 学校や職場で使用できるようなプロフィール欄も自由に作成でき、公開設定も変更できます。
+ QRコードの印刷にも対応していて共有にも最適です。 + `, + ServerAboutTitle: "このサーバーについて", + or: "または", + and: "と", + agree: "同意", + Start: "今すぐ始める", + Signin: "サインイン", + Signup: "サインアップ", + email: "メールアドレス", + password: "パスワード", + username: "ユーザー名", + idHelp: "@から始まる英数字のID(3文字以上)", + mailHelp: "メールアドレスは受信できるものを使用してください", + passwordHelp: "8文字以上のパスワードを使用してください", + signinError: "サインインに失敗しました", + mailVerify: "メール認証", + code: "コード", + submit: "送信", + codeCharacterCount: "コードは6桁で入力してください", + emailVerificationSuccess: ` + メール認証に成功しました。 + ログインしてください。 + `, + connectionError: "通信エラー", + accountCreateSuccess: ` + アカウントの作成に成功しました。 + ご登録いただいたメールアドレスに確認用メールを送信しました。 + 確認メールをクリックしてアカウントを有効化してください。 + `, + sameUsername: "同じユーザー名が既に使用されています", + termsAgreement: "利用規約とプライバシーポリシーに同意してください", + bodyRequired: "フォーム内容が送信されていません", + passwordCharacterLimit: "パスワードは8文字以上にしてください", + usernameRestriction: "ユーザー名は3文字以上の英数字のIDにしてください", + userCreationFailed: "ユーザーの作成に失敗しました", + terms: "利用規約", + privacypolicy: "プライバシーポリシー", + }, + en: { + PeasAboutTitle: "About Peas", + PeasAboutText: ` + Peas is a SNS designed for sharing profiles.
+ You can register and share your user profile.
+ You can freely create profile sections suitable for use at school or work, and also change the privacy settings.
+ It also supports printing QR codes, making sharing easy. + `, + ServerAboutTitle: "About this server", + or: "or", + and: "and", + agree: "Agree", + Start: "Get started now", + Signin: "Sign In", + Signup: "Sign Up", + email: "Email", + password: "Password", + username: "Username", + idHelp: + "ID starting with @ and consisting of English numbers (at least 3 characters)", + mailHelp: "Use an email address that can be received", + passwordHelp: "Use a password of at least 8 characters", + signinError: "Sign in failed", + mailVerify: "Mail Verify", + code: "Code", + submit: "Submit", + codeCharacterCount: "Please enter a 6-digit code", + emailVerificationSuccess: ` + Email verification successful. + Please log in. + `, + connectionError: "Connection Error", + accountCreateSuccess: ` + Your account has been successfully created. + We have sent a confirmation email to the email address you registered. + Please click the confirmation email to activate your account. + `, + sameUsername: "That username is already taken", + termsAgreement: "Please agree to the Terms of Service and Privacy Policy", + bodyRequired: "Form contents have not been submitted", + passwordCharacterLimit: + "Please make your password at least 8 characters long.", + usernameRestriction: + "Please make your username an alphanumeric ID with at least 3 characters.", + userCreationFailed: "Failed to create the user.", + terms: "Terms of Service", + privacypolicy: "Privacy Policy", + }, +}; diff --git a/src/lib/database.ts b/src/lib/database.ts index 14ca7ca..ee03b31 100644 --- a/src/lib/database.ts +++ b/src/lib/database.ts @@ -1,13 +1,14 @@ -import mysql from 'mysql2/promise'; +import mysql from "mysql2/promise"; +import PeasConfig from "@/../peas.config"; const pool = mysql.createPool({ - host: process.env.DB_HOST, - port: Number(process.env.DB_PORT), - user: process.env.DB_USER, - password: process.env.DB_PASSWORD, - database: process.env.DB_DATABASE, - waitForConnections: true, - connectionLimit: 10, + host: PeasConfig.databaseHost, + port: PeasConfig.databasePort, + user: PeasConfig.databaseUser, + password: PeasConfig.databasePassword, + database: PeasConfig.databaseDB, + waitForConnections: true, + connectionLimit: 10, }); -export default pool; \ No newline at end of file +export default pool; diff --git a/src/lib/mailsend.ts b/src/lib/mailsend.ts index c4b732f..e7f66cb 100644 --- a/src/lib/mailsend.ts +++ b/src/lib/mailsend.ts @@ -1,60 +1,51 @@ -import * as nodemailer from 'nodemailer'; -import * as dotenv from 'dotenv'; - -dotenv.config(); +import * as nodemailer from "nodemailer"; +import type SMTPTransport from "nodemailer/lib/smtp-transport"; +import PeasConfig from "@/../peas.config"; export interface EmailMessage { - to: string | string[], - subject: string, - text?: string, - html?: string, + to: string | string[]; + subject: string; + text?: string; + html?: string; } export async function createTransporter() { - // 環境変数の値をログ出力して確認 - console.log('SMTP設定:', { - host: process.env.SMTP_HOST, - port: process.env.SMTP_PORT, - secure: process.env.SMTP_SECURE, - user: process.env.SMTP_USER - }); + const transporter = nodemailer.createTransport({ + host: PeasConfig.smtpHost, + port: PeasConfig.smtpPort, + secure: PeasConfig.smtpSecure, + auth: { + user: PeasConfig.smtpUser, + pass: PeasConfig.smtpPassword, + }, + debug: process.env.DEBUG === "true", + } as SMTPTransport.Options); - const transporter = nodemailer.createTransport({ - host: process.env.SMTP_HOST, - port: Number(process.env.SMTP_PORT), - secure: process.env.SMTP_SECURE === 'true', - auth: { - user: process.env.SMTP_USER, - pass: process.env.SMTP_PASSWORD, - }, - debug: process.env.DEBUG === 'true', - }); + // 接続テスト + try { + await transporter.verify(); + console.log("SMTPサーバーに接続できました"); + } catch (error) { + console.error("SMTP接続テストに失敗:", error); + throw error; + } - // 接続テスト - try { - await transporter.verify(); - console.log('SMTPサーバーに接続できました'); - } catch (error) { - console.error('SMTP接続テストに失敗:', error); - throw error; - } - - return transporter; + return transporter; } export async function sendMail(message: EmailMessage): Promise { - try{ - const transporter = await createTransporter(); - await transporter.sendMail({ - from: process.env.SMTP_USER, - to: Array.isArray(message.to) ? message.to.join(',') : message.to, - subject: message.subject, - text: message.text, - html: message.html - }); - console.log('メール送信成功'); - }catch (error){ - console.error('メール送信に失敗しました:', error); - throw error; - } -} \ No newline at end of file + try { + const transporter = await createTransporter(); + await transporter.sendMail({ + from: PeasConfig.smtpUser, + to: Array.isArray(message.to) ? message.to.join(",") : message.to, + subject: message.subject, + text: message.text, + html: message.html, + }); + console.log("メール送信成功"); + } catch (error) { + console.error("メール送信に失敗しました:", error); + throw error; + } +} diff --git a/src/lib/peas.config.d.ts b/src/lib/peas.config.d.ts new file mode 100644 index 0000000..18c338e --- /dev/null +++ b/src/lib/peas.config.d.ts @@ -0,0 +1,24 @@ +interface SSLConfig { + ssl: boolean; +} + +export interface PeasConfigType { + // Server Config + serverName: string; + serverDescription: string; + serverHost: string; + // SSL Config + ssl: SSLConfig; + // SMTP Config + smtpHost: string; + smtpPort: number; + smtpSecure: boolean; + smtpUser: string; + smtpPassword: string; + // Database Config + databaseHost: string; + databasePort: number; + databaseUser: string; + databasePassword: string; + databaseDB: string; +}