diff --git a/backend/main.go b/backend/main.go index f481899..5575cd0 100644 --- a/backend/main.go +++ b/backend/main.go @@ -21,11 +21,20 @@ type Handler struct { } type Post struct { - ID string `db:"id" json:"id"` - Body string `db:"body" json:"body"` + ID string `db:"id" json:"id"` + Body string `db:"body" json:"body"` CreatedAt time.Time `db:"created_at" json:"created_at"` } +type User struct { + ID string `db:"id" json:"id"` + Name string `db:"username" json:"name"` + CreatedAt string `db:"created_at" json:"created_at"` + UpdatedAt string `db:"updated_at" json:"updated_at"` + ProfileImageURL string `db:"profile_image_url" json:"profile_image_url"` + Bio string `db:"bio" json:"bio"` +} + var ( SQL_PATH = "./sql" IMAGES_DIR = "./public/images" @@ -104,6 +113,8 @@ func main() { e.Logger.Info("health check") return c.JSON(200, "ok") }) + api.GET("/users", h.GetUsers) + api.POST("/users/new", h.CreateUser) e.Logger.Fatal(e.Start(":8000")) } @@ -199,3 +210,36 @@ func (h *Handler) CreatePost(c echo.Context) error { return c.JSON(200, post) } + +func (h *Handler) GetUsers(c echo.Context) error { + users := []User{} + err := h.DB.Select(&users, "SELECT * FROM users") + if err != nil { + h.Logger.Error(err) + return c.JSON(500, err) + } + return c.JSON(200, users) +} + +func (h *Handler) CreateUser(c echo.Context) error { + id, err := uuid.NewRandom() + if err != nil { + h.Logger.Error(err) + return c.JSON(500, err) + } + user := new(User) + if err := c.Bind(user); err != nil { + h.Logger.Error(err) + return c.JSON(500, err) + } + user.ID = id.String() + user.CreatedAt = time.Now().Format("2006-01-02 15:04:05") + user.UpdatedAt = time.Now().Format("2006-01-02 15:04:05") + + _, err = h.DB.Exec("INSERT INTO users (id, username, created_at, updated_at, profile_image_url, bio) VALUES (?, ?, ?, ?, ?, ?)", user.ID, user.Name, user.CreatedAt, user.UpdatedAt, user.ProfileImageURL, user.Bio) + if err != nil { + h.Logger.Error(err) + return c.JSON(500, err) + } + return c.JSON(200, user) +} diff --git a/backend/sql/1_schema.sql b/backend/sql/1_create_post_table.sql similarity index 100% rename from backend/sql/1_schema.sql rename to backend/sql/1_create_post_table.sql diff --git a/backend/sql/2_create_user_table.sql b/backend/sql/2_create_user_table.sql new file mode 100644 index 0000000..f548d04 --- /dev/null +++ b/backend/sql/2_create_user_table.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS `users` ( + `id` varchar(36) NOT NULL COMMENT 'ユーザーID', + `username` varchar(255) NOT NULL COMMENT 'ユーザー名', + `created_at` datetime NOT NULL COMMENT '登録日時', + `updated_at` datetime NOT NULL COMMENT '更新日時', + `profile_image_url` varchar(255) DEFAULT NULL COMMENT 'プロフィール画像のURL', + `bio` varchar(1024) DEFAULT NULL COMMENT '自己紹介文', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 24a698d..03d6b4f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,7 +14,7 @@ "@mui/material": "^5.14.11", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.17.0" + "react-router-dom": "^6.19.0" }, "devDependencies": { "@types/react": "^18.2.15", @@ -1370,9 +1370,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.10.0.tgz", - "integrity": "sha512-Lm+fYpMfZoEucJ7cMxgt4dYt8jLfbpwRCzAjm9UgSLOkmlqo9gupxt6YX3DY0Fk155NT9l17d/ydi+964uS9Lw==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.12.0.tgz", + "integrity": "sha512-2hXv036Bux90e1GXTWSMfNzfDDK8LA8JYEWfyHxzvwdp6GyoWEovKc9cotb3KCKmkdwsIBuFGX7ScTWyiHv7Eg==", "engines": { "node": ">=14.0.0" } @@ -3733,11 +3733,11 @@ } }, "node_modules/react-router": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.17.0.tgz", - "integrity": "sha512-YJR3OTJzi3zhqeJYADHANCGPUu9J+6fT5GLv82UWRGSxu6oJYCKVmxUcaBQuGm9udpWmPsvpme/CdHumqgsoaA==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.19.0.tgz", + "integrity": "sha512-0W63PKCZ7+OuQd7Tm+RbkI8kCLmn4GPjDbX61tWljPxWgqTKlEpeQUwPkT1DRjYhF8KSihK0hQpmhU4uxVMcdw==", "dependencies": { - "@remix-run/router": "1.10.0" + "@remix-run/router": "1.12.0" }, "engines": { "node": ">=14.0.0" @@ -3747,12 +3747,12 @@ } }, "node_modules/react-router-dom": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.17.0.tgz", - "integrity": "sha512-qWHkkbXQX+6li0COUUPKAUkxjNNqPJuiBd27dVwQGDNsuFBdMbrS6UZ0CLYc4CsbdLYTckn4oB4tGDuPZpPhaQ==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.19.0.tgz", + "integrity": "sha512-N6dWlcgL2w0U5HZUUqU2wlmOrSb3ighJmtQ438SWbhB1yuLTXQ8yyTBMK3BSvVjp7gBtKurT554nCtMOgxCZmQ==", "dependencies": { - "@remix-run/router": "1.10.0", - "react-router": "6.17.0" + "@remix-run/router": "1.12.0", + "react-router": "6.19.0" }, "engines": { "node": ">=14.0.0" diff --git a/frontend/package.json b/frontend/package.json index 443b101..840290e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,7 +17,7 @@ "@mui/material": "^5.14.11", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.17.0" + "react-router-dom": "^6.19.0" }, "devDependencies": { "@types/react": "^18.2.15", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index ea85a92..25934dc 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,10 +1,11 @@ import { CssBaseline } from "@mui/material"; import { GlobalStyles } from "@mui/material"; -import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; +import { BrowserRouter as Router, Route, Routes,Link } from "react-router-dom"; import { Header } from "./components/Header"; import { ColorModeProvider } from "./components/theme/ColorModeProvider.jsx"; import { ToggleTheme } from "./components/theme/ToggleTheme.jsx"; import { Home } from "./pages/Home.jsx"; +import { Registration } from "./pages/Registration.jsx"; function App() { return ( @@ -22,7 +23,8 @@ function App() { /> - }> + }> + }> diff --git a/frontend/src/components/Form.jsx b/frontend/src/components/Form.jsx index 2df95e8..c1e4a26 100644 --- a/frontend/src/components/Form.jsx +++ b/frontend/src/components/Form.jsx @@ -1,16 +1,16 @@ import { Box, Button, FormLabel, TextField } from "@mui/material"; import { useState } from "react"; -export const Form = ({ onSubmitted }) => { +export const Form = ({ onSubmitted ,selectedUser}) => { const [body, setBody] = useState(""); const [isSending, setIsSending] = useState(false); const sendPost = async (e) => { e.preventDefault(); setIsSending(true); - const res = await fetch("/api/posts", { + const res = await fetch(`/api/posts`, { method: "POST", - body: JSON.stringify({ body }), + body: JSON.stringify({ body,user_id:selectedUser }), headers: { "Content-Type": "application/json", }, diff --git a/frontend/src/components/UserSelectBox.jsx b/frontend/src/components/UserSelectBox.jsx new file mode 100644 index 0000000..9a2016c --- /dev/null +++ b/frontend/src/components/UserSelectBox.jsx @@ -0,0 +1,28 @@ +import { + FormControl, + InputLabel, + Select, + MenuItem, +} from '@mui/material'; + + +export const UserSelectBox = ({users,selectedUser, handleChange}) =>{ + return( + <> + + User + + + + ) +} \ No newline at end of file diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 565c207..7cf636e 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -2,10 +2,14 @@ import { Container } from "@mui/material"; import { useEffect, useState } from "react"; import { Form } from "../components/Form"; import { Timeline } from "../components/Timeline"; +import { UserSelectBox } from "../components/UserSelectBox"; +import { Link } from "react-router-dom"; export const Home = () => { const [posts, setPosts] = useState([]); + const [users, setUsers] = useState([]); const [isLoading, setIsLoading] = useState(false); + const [selectedUser, setSelectedUser] = useState('');//現在選択されているユーザーidを保持する変数 const onSubmitted = (post) => { setPosts([post, ...posts]); }; @@ -22,8 +26,23 @@ export const Home = () => { setIsLoading(false); }; + const fetchUsers = async () =>{ + const res = await fetch("/api/users"); + if(res.ok){ + const data = await res.json(); + setUsers(data); + } else{ + console.error(data); + } + } + //セレクトボックスの人が変更されたときに呼ばれるハンドラ関数 + const handleChange = (event) => { + setSelectedUser(event.target.value); + }; + useEffect(() => { fetchPosts(); + fetchUsers(); }, []); return ( @@ -33,8 +52,24 @@ export const Home = () => { py: 3, }} > -
- +
+ ユーザー登録 +
+ + + + ); }; diff --git a/frontend/src/pages/Registration.jsx b/frontend/src/pages/Registration.jsx new file mode 100644 index 0000000..e668e7b --- /dev/null +++ b/frontend/src/pages/Registration.jsx @@ -0,0 +1,120 @@ +import { Container, Snackbar } from "@mui/material"; +import { Box, Button, FormLabel, TextField } from "@mui/material"; +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; + +export const Registration = () => { + const [name, setName] = useState(""); + const [profileURL, setProfileURL] = useState(""); + const [bio, setBio] = useState(""); + const [snackbarOpen, setSnackbarOpen] = useState(false); + + const navigate = useNavigate(); + const handleCloseSnackbar = () =>{ + setSnackbarOpen(false); + }; + const sendPost = async (e) => { + e.preventDefault(); + const res = await fetch(`/api/users/new`, { + method: "POST", + body: JSON.stringify({ name, profileURL, bio }), + headers: { + "Content-Type": "application/json", + }, + }); + setName(""); + setProfileURL(""); + setBio(""); + + if(res.ok){ + setSnackbarOpen(true); + + setTimeout(() => { + setSnackbarOpen(false); + navigate("/"); + },2000); + + } + }; + + return ( + <> + + + ユーザー登録 + + setName(e.target.value)} + /> + setProfileURL(e.target.value)} + /> + setBio(e.target.value)} + /> + + + + + + + + + + + + + ); +}; \ No newline at end of file