From 8671a058be4630e45e08c4a08455b95340e9972c Mon Sep 17 00:00:00 2001 From: Ken Yasue Date: Sun, 1 Jun 2025 22:36:04 +0200 Subject: [PATCH] add relations --- app/api/data/route.ts | 34 ++++ app/api/posts/route.ts | 64 +++++++ app/page.tsx | 328 ++++++++++++++++++++++++++++++++++-- schema/entity/PostEntity.ts | 34 ++++ schema/entity/TagEntity.ts | 18 ++ schema/entity/UserEntity.ts | 8 +- schema/index.ts | 10 +- 7 files changed, 476 insertions(+), 20 deletions(-) create mode 100644 app/api/data/route.ts create mode 100644 app/api/posts/route.ts create mode 100644 schema/entity/PostEntity.ts create mode 100644 schema/entity/TagEntity.ts diff --git a/app/api/data/route.ts b/app/api/data/route.ts new file mode 100644 index 0000000..130199c --- /dev/null +++ b/app/api/data/route.ts @@ -0,0 +1,34 @@ +import { NextResponse } from 'next/server'; +import { db } from '@/app/connection'; + +export async function GET() { + try { + const database = await db; + + // Fetch all users + const users = await database.userRepository.find(); + + // Fetch all posts with their related users and tags + const posts = await database.postRepository.find({ + relations: ['user', 'tags'], + order: { createdAt: 'DESC' } + }); + + // Fetch all tags + const tags = await database.tagRepository.find({ + order: { name: 'ASC' } + }); + + return NextResponse.json({ + users, + posts, + tags + }); + } catch (error) { + console.error('Error fetching data:', error); + return NextResponse.json( + { error: 'Failed to fetch data' }, + { status: 500 } + ); + } +} diff --git a/app/api/posts/route.ts b/app/api/posts/route.ts new file mode 100644 index 0000000..c3d3783 --- /dev/null +++ b/app/api/posts/route.ts @@ -0,0 +1,64 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { db } from '@/app/connection'; + +export async function POST(request: NextRequest) { + try { + const { content, username, email } = await request.json(); + + if (!content || !username || !email) { + return NextResponse.json( + { error: 'Content, username, and email are required' }, + { status: 400 } + ); + } + + const database = await db; + + // Check if user exists, if not create one + let user = await database.userRepository.findOne({ + where: { email } + }); + + if (!user) { + user = database.userRepository.create({ + name: username, + email: email, + }); + await database.userRepository.save(user); + } + + // Extract hashtags from content + const hashtagRegex = /#([a-zA-Z0-9_]+)/g; + const hashtags = [...content.matchAll(hashtagRegex)].map(match => match[1].toLowerCase()); + const uniqueHashtags = [...new Set(hashtags)]; + + // Create or find tags + const tags = []; + for (const tagName of uniqueHashtags) { + let tag = await database.tagRepository.findOne({ where: { name: tagName } }); + if (!tag) { + tag = database.tagRepository.create({ name: tagName }); + await database.tagRepository.save(tag); + } + tags.push(tag); + } + + // Create the post + const post = database.postRepository.create({ + content, + username, + userEmail: email, + tags + }); + + const savedPost = await database.postRepository.save(post); + + return NextResponse.json(savedPost, { status: 201 }); + } catch (error) { + console.error('Error creating post:', error); + return NextResponse.json( + { error: 'Failed to create post' }, + { status: 500 } + ); + } +} diff --git a/app/page.tsx b/app/page.tsx index dfb0d4d..6defabb 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,21 +1,319 @@ -import Image from "next/image"; -import { db } from "./connection"; +"use client"; +import { useState, useEffect } from "react"; +import { UserEntity } from "@/schema/entity/UserEntity"; +import { PostEntity } from "@/schema/entity/PostEntity"; +import { TagEntity } from "@/schema/entity/TagEntity"; + +interface PostWithUser extends PostEntity { + user: UserEntity | null; + tags: TagEntity[]; +} + +export default function Home() { + const [posts, setPosts] = useState([]); + const [users, setUsers] = useState([]); + const [tags, setTags] = useState([]); + const [filteredPosts, setFilteredPosts] = useState([]); + const [selectedUser, setSelectedUser] = useState(null); + const [selectedTag, setSelectedTag] = useState(null); + const [content, setContent] = useState(""); + const [username, setUsername] = useState(""); + const [email, setEmail] = useState(""); + const [loading, setLoading] = useState(false); + + useEffect(() => { + fetchData(); + }, []); + + useEffect(() => { + let filtered = posts; + + if (selectedUser) { + filtered = filtered.filter(post => post.userEmail === selectedUser); + } + + if (selectedTag) { + filtered = filtered.filter(post => + post.tags && post.tags.some(tag => tag.name === selectedTag) + ); + } + + setFilteredPosts(filtered); + }, [posts, selectedUser, selectedTag]); + + const fetchData = async () => { + try { + const response = await fetch('/api/data'); + const data = await response.json(); + setPosts(data.posts || []); + setUsers(data.users || []); + setTags(data.tags || []); + } catch (error) { + console.error('Error fetching data:', error); + } + }; + + const handleSubmitPost = async (e: React.FormEvent) => { + e.preventDefault(); + if (!content.trim() || !username.trim() || !email.trim()) { + alert('Please fill in all fields'); + return; + } + + setLoading(true); + try { + const response = await fetch('/api/posts', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + content: content.trim(), + username: username.trim(), + email: email.trim(), + }), + }); + + if (response.ok) { + setContent(""); + setUsername(""); + setEmail(""); + await fetchData(); // Refresh data + } else { + alert('Failed to create post'); + } + } catch (error) { + console.error('Error creating post:', error); + alert('Error creating post'); + } finally { + setLoading(false); + } + }; + + const handleUserClick = (userEmail: string) => { + if (selectedUser === userEmail) { + setSelectedUser(null); // Deselect if clicking same user + } else { + setSelectedUser(userEmail); + setSelectedTag(null); // Clear tag filter when selecting user + } + }; + + const handleTagClick = (tagName: string) => { + if (selectedTag === tagName) { + setSelectedTag(null); // Deselect if clicking same tag + } else { + setSelectedTag(tagName); + setSelectedUser(null); // Clear user filter when selecting tag + } + }; + + const clearAllFilters = () => { + setSelectedUser(null); + setSelectedTag(null); + }; + + const renderPostContent = (content: string) => { + // Convert hashtags to clickable elements + const parts = content.split(/(#[a-zA-Z0-9_]+)/g); + return parts.map((part, index) => { + if (part.match(/^#[a-zA-Z0-9_]+$/)) { + const tagName = part.slice(1).toLowerCase(); + return ( + handleTagClick(tagName)} + > + {part} + + ); + } + return part; + }); + }; -export default async function Home() { - const user = await db.userRepository.findOneBy({email: "montheeric1@gmail.com"}) - return ( -
-
{user?.email}
-
- user avatar +
+

Posts, Users & Tags

+ + {/* Post Creation Form */} +
+

Create New Post

+

+ Use hashtags like #javascript #coding to categorize your posts! +

+
+
+ + setUsername(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" + placeholder="Enter your username" + required + /> +
+ +
+ + setEmail(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" + placeholder="Enter your email" + required + /> +
+ +
+ +