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 718d6fe..f4c98e2 100644 Binary files a/src/app/favicon.ico and b/src/app/favicon.ico differ 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; +}