Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions frontend/auth.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { NextAuthConfig } from "next-auth";
import Google from "next-auth/providers/google";

const allowedEmails = process.env.ALLOWED_EMAILS?.split(",") || []; // 環境変数から許可されるメールアドレスを取得

export const authConfig: NextAuthConfig = {
providers: [Google],
callbacks: {
async signIn({ user }) {
if (allowedEmails.includes(user.email || "")) {
console.log("許可されたメールアドレス:", user.email);
return true; // 許可されたメールアドレスの場合ログインを許可
}
console.log("許可されていないメールアドレス:", user.email);
return false; // 許可されていない場合ログインを拒否
},
async jwt({ token, user, account }) {
if (user && account?.id_token) {
token.idToken = account?.id_token;
}
return token;
},
async session({ token, session }) {
session.idToken = token.idToken;
return session;
},
},
};
4 changes: 4 additions & 0 deletions frontend/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import NextAuth from "next-auth";
import { authConfig } from "./auth.config";

export const { handlers, auth } = NextAuth(authConfig);
1 change: 1 addition & 0 deletions frontend/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { auth as middleware } from "./auth"
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"axios": "^1.7.2",
"framer-motion": "^11.1.9",
"next": "^14.2.23",
"next-auth": "^5.0.0-beta.27",
"openai": "^4.77.0",
"postcss": "8.4.38",
"react": "18.3.1",
Expand Down
245 changes: 143 additions & 102 deletions frontend/src/app/(main)/documents/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
"use client";

import { useState, useEffect, Suspense } from "react";
import { FaTrash } from "react-icons/fa";
import { FaTrash, FaEdit, FaPlus } from "react-icons/fa"; // FaEdit, FaPlusを追加
import { CategoryItem, CosmosItem } from "@/models/models";
import CategoryDropdown from "../../../components/documents/CategoryDropdown";
import DocumentForm from "../../../components/documents/DocumentForm";
import DocumentList from "../../../components/documents/DocumentList";
import CategoryModal from "@/components/documents/CategoryModal";
import Head from "next/head"; // Headを追加

export default function DocumentsPage() {
const [selectedCategory, setSelectedCategory] = useState("");
Expand Down Expand Up @@ -113,109 +114,149 @@ export default function DocumentsPage() {
};

return (
<main className="flex flex-col text-gray-800 w-full h-full overflow-y-auto">
<h1 className="text-xl font-bold text-center">ドキュメント管理</h1>
<div className="flex justify-center items-center p-4 space-x-2">
<Suspense fallback={<p>カテゴリを取得中...</p>}>
<CategoryDropdown
categories={categories}
onCategoryChange={handleCategoryChange}
onAddCategory={() => {
setEditingCategory(null);
setIsCategoryModalOpen(true);
}}
value={selectedCategory} // ドロップダウンの選択状態をバインド
/>
</Suspense>
<>
<Head>
<title>ドキュメント管理</title> {/* タイトルを設定 */}
</Head>
<main className="flex flex-col text-gray-800 w-full h-full overflow-y-auto">
<h1 className="text-xl font-bold text-center">ドキュメント管理</h1>
<div className="flex justify-center items-center p-4 space-x-2">
<Suspense fallback={<p>カテゴリを取得中...</p>}>
<CategoryDropdown
categories={categories}
onCategoryChange={handleCategoryChange}
onAddCategory={() => {
setEditingCategory(null);
setIsCategoryModalOpen(true);
}}
value={selectedCategory} // ドロップダウンの選択状態をバインド
/>
</Suspense>
{selectedCategory && (
<div className="flex items-center space-x-2">
<button
className="bg-yellow-500 text-white p-2 rounded-full"
onClick={() => {
const categoryToEdit = categories.find((cat) => cat.id === selectedCategory);
setEditingCategory(categoryToEdit || null);
setIsCategoryModalOpen(true);
}}
>
<FaEdit />
</button>
<button
className="bg-blue-500 text-white p-2 rounded-full"
onClick={() => {
setEditingCategory(null);
setIsCategoryModalOpen(true);
}}
>
<FaPlus />
</button>
<button
className="bg-red-500 text-white p-2 rounded-full"
onClick={() => handleDeleteCategory(selectedCategory)}
>
<FaTrash />
</button>
</div>
)}
</div>
{selectedCategory && (
<button
className="bg-red-500 text-white p-2 rounded-full"
onClick={() => handleDeleteCategory(selectedCategory)}
>
<FaTrash />
</button>
)}
</div>
{selectedCategory && (
<div className="flex flex-col md:flex-row flex-1">
<DocumentForm
text={text}
setText={setText}
isSubmitting={isSubmitting}
onSubmit={async () => {
if (!text.trim()) return alert("テキストを入力してください。");
if (!selectedCategory) return alert("カテゴリを選択してください。");

setIsSubmitting(true);
try {
const method = editingDocument ? "PATCH" : "POST";
const url = `/api/document`;

const payload = {
category_id: selectedCategory,
content: text,
};

const response = await fetch(url, {
method,
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});

if (response.ok) {
alert(editingDocument ? "更新が成功しました!" : "登録が成功しました!");
setText("");
setEditingDocument(null);
fetchDocuments(selectedCategory);
} else {
alert(editingDocument ? "更新に失敗しました。" : "登録に失敗しました。");
<div className="flex flex-col md:flex-row flex-1">
<DocumentForm
text={text}
setText={setText}
isSubmitting={isSubmitting}
onSubmit={async () => {
if (!text.trim()) {
alert("テキストを入力してください。");
return;
}
} catch (error) {
console.error("エラー:", error);
alert("エラーが発生しました。");
} finally {
setIsSubmitting(false);
}
}}
editingDocument={editingDocument}
/>
<DocumentList
documents={documents}
onEdit={(doc) => {
setEditingDocument(doc);
setText(doc.content);
}}
onDelete={async (docId) => {
if (!confirm("本当にこのドキュメントを削除しますか?")) return;

try {
const response = await fetch(`/api/document`, {
method: "DELETE",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ id: docId }),
});

if (response.ok) {
alert("削除が成功しました!");
fetchDocuments(selectedCategory);
} else {
const errorData = await response.json();
alert(`削除に失敗しました: ${errorData.message}`);
if (!selectedCategory) {
alert("カテゴリを選択してください。");
return;
}
} catch (error) {
console.error("エラー:", error);
alert("エラーが発生しました。");
}
}}
/>
</div>
)}
<CategoryModal
isOpen={isCategoryModalOpen}
onClose={() => setIsCategoryModalOpen(false)}
onSubmit={handleCategorySubmit}
initialCategory={editingCategory?.category}
/>
</main>

setIsSubmitting(true);
try {
const method = editingDocument ? "PATCH" : "POST";
const url = `/api/document`;

const payload = {
id: editingDocument?.id, // 更新時はIDを含める
category_id: selectedCategory,
content: text,
};

const response = await fetch(url, {
method,
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});

if (response.ok) {
const message = editingDocument ? "更新が成功しました!" : "登録が成功しました!";
alert(message);
setText("");
setEditingDocument(null);
fetchDocuments(selectedCategory);
} else {
const errorData = await response.json();
const message = editingDocument ? "更新に失敗しました。" : "登録に失敗しました。";
alert(`${message} エラー: ${errorData.message}`);
}
} catch (error) {
console.error("エラー:", error);
alert("エラーが発生しました。");
} finally {
setIsSubmitting(false);
}
}}
editingDocument={editingDocument}
/>
<DocumentList
documents={documents}
onEdit={(doc) => {
setEditingDocument(doc);
setText(doc.content);
}}
onDelete={async (docId) => {
if (!confirm("本当にこのドキュメントを削除しますか?")) return;

try {
const response = await fetch(`/api/document`, {
method: "DELETE",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ id: docId }),
});

if (response.ok) {
alert("削除が成功しました!");
fetchDocuments(selectedCategory);
} else {
const errorData = await response.json();
alert(`削除に失敗しました: ${errorData.message}`);
}
} catch (error) {
console.error("エラー:", error);
alert("エラーが発生しました。");
}
}}
onAdd={() => {
setEditingDocument(null); // 新規登録モードに切り替え
setText(""); // フォームをクリア
}}
/>
</div>
)}
<CategoryModal
isOpen={isCategoryModalOpen}
onClose={() => setIsCategoryModalOpen(false)}
onSubmit={handleCategorySubmit}
initialCategory={editingCategory?.category}
/>
</main>
</>
);
}
1 change: 0 additions & 1 deletion frontend/src/app/(main)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const MainLayout = ({ children }: { children: React.ReactNode }) => {
return (
<div className='flex flex-col h-screen'>
<Provider store={store}>
{/* Adjust Header to avoid duplication with page-specific titles */}
<Header />
<main className='bg-slate-50 flex-1 overflow-auto p-2 sm:p-1 md:p-2'>
{children}
Expand Down
30 changes: 22 additions & 8 deletions frontend/src/app/(main)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
"use client";

import { useSession } from "next-auth/react";
import FormInput from "@/components/FormInput/FormInput";
import MessageArea from "@/components/MessageArea/MessageArea";
import Head from "next/head"; // Headを追加

export default function Home() {
const { data: session } = useSession();

console.log(session?.idToken); // ID トークンを sessionに格納できている
console.log(session?.user?.email); // auth() と同様に取得できる

return (
<main className="flex flex-col text-gray-800 w-full h-full overflow-y-auto">
<div className="flex bg-slate-300 h-5/6 justify-center">
<MessageArea />
</div>
<div className="flex h-1/6 justify-center items-center">
<FormInput />
</div>
</main>
<>
<Head>
<title>AIチャット</title> {/* タイトルを設定 */}
</Head>
<main className="flex flex-col text-gray-800 w-full h-full overflow-y-auto">
<div className="flex bg-slate-300 h-5/6 justify-center">
<MessageArea />
</div>
<div className="flex h-1/6 justify-center items-center">
<FormInput />
</div>
</main>
</>
);
}
3 changes: 3 additions & 0 deletions frontend/src/app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { handlers } from "../../../../../auth";

export const { GET, POST } = handlers;
6 changes: 6 additions & 0 deletions frontend/src/app/api/auth/allowed-emails/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { NextResponse } from "next/server";

export async function GET() {
const allowedEmails = process.env.ALLOWED_EMAILS?.split(",") || [];
return NextResponse.json(allowedEmails);
}
Loading