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 (
-
-
-
- {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 (
-
- )
-}
\ 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 (
-