From 7a0bb63e7bc1421d0bdac119b8e55321bc6f6aa9 Mon Sep 17 00:00:00 2001 From: Takeshi Kimata <117462761+kimatata@users.noreply.github.com> Date: Wed, 22 May 2024 22:33:21 +0900 Subject: [PATCH] Make project table associated with the user --- ...sers.js => 20240201220834-create-users.js} | 0 .../20240204090345-create-projects.js | 14 +++++++++ backend/models/projects.js | 13 ++++++++ backend/models/users.js | 6 ++-- backend/routes/projects/edit.js | 3 +- backend/routes/projects/new.js | 4 ++- backend/seeders/seed.js | 21 ++++++++++++- frontend/messages/en.json | 6 +++- frontend/messages/ja.json | 6 +++- frontend/src/app/[locale]/DropdownAccount.tsx | 17 ++++++++-- .../src/app/[locale]/account/authPage.tsx | 2 +- .../[locale]/projects/NeedSignedInDialog.tsx | 31 +++++++++++++++++++ .../app/[locale]/projects/ProjectDialog.tsx | 26 ++++++++++++++-- .../app/[locale]/projects/ProjectsPage.tsx | 24 +++++++++++--- frontend/src/app/[locale]/projects/page.tsx | 4 +++ .../app/[locale]/projects/projectsControl.ts | 15 +++++---- frontend/types/project.ts | 6 ++++ 17 files changed, 174 insertions(+), 24 deletions(-) rename backend/migrations/{20240517220834-create-users.js => 20240201220834-create-users.js} (100%) create mode 100644 frontend/src/app/[locale]/projects/NeedSignedInDialog.tsx diff --git a/backend/migrations/20240517220834-create-users.js b/backend/migrations/20240201220834-create-users.js similarity index 100% rename from backend/migrations/20240517220834-create-users.js rename to backend/migrations/20240201220834-create-users.js diff --git a/backend/migrations/20240204090345-create-projects.js b/backend/migrations/20240204090345-create-projects.js index 3096a37..4fbf7a9 100644 --- a/backend/migrations/20240204090345-create-projects.js +++ b/backend/migrations/20240204090345-create-projects.js @@ -17,6 +17,20 @@ module.exports = { type: Sequelize.STRING, allowNull: true, }, + isPublic: { + type: Sequelize.BOOLEAN, + allowNull: false, + }, + userId: { + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: 'users', + key: 'id', + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }, + }, createdAt: { type: Sequelize.DATE, allowNull: false, diff --git a/backend/models/projects.js b/backend/models/projects.js index 2f85d75..c56293e 100644 --- a/backend/models/projects.js +++ b/backend/models/projects.js @@ -8,6 +8,19 @@ function defineProject(sequelize, DataTypes) { type: DataTypes.STRING, allowNull: true, }, + isPublic: { + type: DataTypes.BOOLEAN, + allowNull: false, + }, + userId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'user', + key: 'id', + }, + onDelete: 'CASCADE', + }, }); Project.associate = (models) => { diff --git a/backend/models/users.js b/backend/models/users.js index 42c473c..53d5273 100644 --- a/backend/models/users.js +++ b/backend/models/users.js @@ -22,9 +22,9 @@ function defineUser(sequelize, DataTypes) { }, }); - // User.associate = (models) => { - // User.hasMany(models.Project, { foreignKey: "userId" }); - // }; + User.associate = (models) => { + User.hasMany(models.Project, { foreignKey: 'userId' }); + }; return User; } diff --git a/backend/routes/projects/edit.js b/backend/routes/projects/edit.js index d6b7f5c..a8c2a33 100644 --- a/backend/routes/projects/edit.js +++ b/backend/routes/projects/edit.js @@ -8,7 +8,7 @@ module.exports = function (sequelize) { router.put('/:projectId', async (req, res) => { const projectId = req.params.projectId; - const { name, detail } = req.body; + const { name, detail, isPublic } = req.body; try { const project = await Project.findByPk(projectId); if (!project) { @@ -17,6 +17,7 @@ module.exports = function (sequelize) { await project.update({ name, detail, + isPublic, }); res.json(project); } catch (error) { diff --git a/backend/routes/projects/new.js b/backend/routes/projects/new.js index 69f6e0f..503bdd3 100644 --- a/backend/routes/projects/new.js +++ b/backend/routes/projects/new.js @@ -8,10 +8,12 @@ module.exports = function (sequelize) { router.post('/', async (req, res) => { try { - const { name, detail } = req.body; + const { name, detail, isPublic, userId } = req.body; const newProject = await Project.create({ name, detail, + isPublic, + userId, }); res.json(newProject); } catch (error) { diff --git a/backend/seeders/seed.js b/backend/seeders/seed.js index d0ca6ce..535be73 100644 --- a/backend/seeders/seed.js +++ b/backend/seeders/seed.js @@ -1,20 +1,39 @@ 'use strict'; -const path = require('path'); const fs = require('fs'); +const bcrypt = require('bcrypt'); module.exports = { up: async (queryInterface, Sequelize) => { + const hashedPassword = await bcrypt.hash('password', 10); + + // Add projects table records + await queryInterface.bulkInsert('Users', [ + { + email: 'admin@testplat.com', + password: hashedPassword, + username: 'Admin', + role: 1, + avatarPath: null, + createdAt: new Date(), + updatedAt: new Date(), + }, + ]); + // Add projects table records await queryInterface.bulkInsert('Projects', [ { name: 'TestPlat Test', detail: "Test Plat's Manual test", + userId: 1, + isPublic: true, createdAt: new Date(), updatedAt: new Date(), }, { name: 'Iron Muscle App(筋トレアプリ)', detail: 'リリース前総合評価', + userId: 1, + isPublic: true, createdAt: new Date(), updatedAt: new Date(), }, diff --git a/frontend/messages/en.json b/frontend/messages/en.json index a680fd7..ead6d6d 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -62,11 +62,15 @@ "actions": "Actions", "project_name": "Project Name", "project_detail": "Project Detail", + "public": "Public", + "if_you_make_public": "If you make project public, everyone who has access to this site will be able to view it.", "close": "Close", "create": "Create", "update": "Update", "please_enter": "Please enter project name", - "no_projects_found": "No projects found" + "no_projects_found": "No projects found", + "you_need_signed_in": "You need signed in", + "sign_in": "Sign in" }, "Project": { "home": "Home", diff --git a/frontend/messages/ja.json b/frontend/messages/ja.json index cd9827c..5d3473a 100644 --- a/frontend/messages/ja.json +++ b/frontend/messages/ja.json @@ -61,11 +61,15 @@ "actions": "アクション", "project_name": "プロジェクト名", "project_detail": "プロジェクト詳細", + "public": "パブリック", + "if_you_make_public": "プロジェクトをパブリックにすると、このサイトにアクセスできるすべてのユーザーがプロジェクトを見ることができます。", "close": "閉じる", "create": "作成", "update": "更新", "please_enter": "プロジェクト名を入力してください", - "no_projects_found": "プロジェクトがありません" + "no_projects_found": "プロジェクトがありません", + "you_need_signed_in": "サインインが必要です", + "sign_in": "サインイン" }, "Project": { "home": "ホーム", diff --git a/frontend/src/app/[locale]/DropdownAccount.tsx b/frontend/src/app/[locale]/DropdownAccount.tsx index cfcf847..4a9dd15 100644 --- a/frontend/src/app/[locale]/DropdownAccount.tsx +++ b/frontend/src/app/[locale]/DropdownAccount.tsx @@ -5,6 +5,7 @@ import { useContext } from 'react'; import { TokenContext } from './TokenProvider'; import { useRouter } from '@/src/navigation'; import { AccountDropDownMessages } from '@/types/user'; +import Avatar from 'boring-avatars'; type Props = { messages: AccountDropDownMessages; @@ -22,11 +23,23 @@ export default function DropdownAccount({ messages, locale, onItemPress }: Props router.push(`/`, { locale: locale }); }; + let userAvatar = + context.token && context.token.user ? ( + + ) : ( + + ); + const signinItems = [ { uid: 'account', title: messages.account, - icon: , + icon: userAvatar, onPress: () => { router.push('/account', { locale: locale }); onItemPress(); @@ -67,7 +80,7 @@ export default function DropdownAccount({ messages, locale, onItemPress }: Props return ( - diff --git a/frontend/src/app/[locale]/account/authPage.tsx b/frontend/src/app/[locale]/account/authPage.tsx index 33b41b1..bad45b7 100644 --- a/frontend/src/app/[locale]/account/authPage.tsx +++ b/frontend/src/app/[locale]/account/authPage.tsx @@ -6,7 +6,7 @@ import { Link } from '@/src/navigation'; import { ChevronRight, Eye, EyeOff } from 'lucide-react'; import { UserType, AuthMessages } from '@/types/user'; import { roles } from '@/config/selection'; -import { signUp, signIn, getRandomAvatarPath } from './authControl'; +import { signUp, signIn } from './authControl'; import { isValidEmail, isValidPassword } from './validate'; import { TokenContext } from '../TokenProvider'; import { useRouter } from '@/src/navigation'; diff --git a/frontend/src/app/[locale]/projects/NeedSignedInDialog.tsx b/frontend/src/app/[locale]/projects/NeedSignedInDialog.tsx new file mode 100644 index 0000000..91ebb07 --- /dev/null +++ b/frontend/src/app/[locale]/projects/NeedSignedInDialog.tsx @@ -0,0 +1,31 @@ +'use client'; +import { ProjectsMessages } from '@/types/project'; +import { Link } from '@/src/navigation'; +import { Button, Modal, ModalContent, ModalHeader, ModalFooter } from '@nextui-org/react'; + +type Props = { + isOpen: boolean; + onCancel: () => void; + messages: ProjectsMessages; + locale: string; +}; + +export default function NeedSignedInDialog({ isOpen, onCancel, messages, locale }: Props) { + return ( + { + onCancel(); + }} + > + + {messages.needSignedIn} + + + + + + + + ); +} diff --git a/frontend/src/app/[locale]/projects/ProjectDialog.tsx b/frontend/src/app/[locale]/projects/ProjectDialog.tsx index ad9054e..b9e9dc0 100644 --- a/frontend/src/app/[locale]/projects/ProjectDialog.tsx +++ b/frontend/src/app/[locale]/projects/ProjectDialog.tsx @@ -1,14 +1,24 @@ 'use client'; import React from 'react'; import { useState, useEffect } from 'react'; -import { Button, Input, Textarea, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from '@nextui-org/react'; +import { + Button, + Input, + Textarea, + Checkbox, + Modal, + ModalContent, + ModalHeader, + ModalBody, + ModalFooter, +} from '@nextui-org/react'; import { ProjectType, ProjectsMessages } from '@/types/project'; type Props = { isOpen: boolean; editingProject: ProjectType; onCancel: () => void; - onSubmit: (name: string, detail: string) => void; + onSubmit: (name: string, detail: string, isPublic: boolean) => void; messages: ProjectsMessages; }; @@ -25,6 +35,8 @@ export default function ProjectDialog({ isOpen, editingProject, onCancel, onSubm errorMessage: '', }); + const [isProjectPublic, setIsProjectPublic] = useState(editingProject ? editingProject.isPublic : true); + useEffect(() => { if (editingProject) { setProjectName({ @@ -36,6 +48,8 @@ export default function ProjectDialog({ isOpen, editingProject, onCancel, onSubm ...projectDetail, text: editingProject.detail ? editingProject.detail : '', }); + + setIsProjectPublic(editingProject.isPublic); } else { setProjectName({ ...projectName, @@ -46,6 +60,8 @@ export default function ProjectDialog({ isOpen, editingProject, onCancel, onSubm ...projectDetail, text: '', }); + + setIsProjectPublic(true); } }, [editingProject]); @@ -73,7 +89,7 @@ export default function ProjectDialog({ isOpen, editingProject, onCancel, onSubm return; } - onSubmit(projectName.text, projectDetail.text); + onSubmit(projectName.text, projectDetail.text, isProjectPublic); clear(); }; @@ -112,6 +128,10 @@ export default function ProjectDialog({ isOpen, editingProject, onCancel, onSubm }); }} /> + + {messages.public} + +
{messages.ifYouMakePublic}