From 9728eb9cf323a5186efb54f06e70140f7e67220c Mon Sep 17 00:00:00 2001 From: AmrMohamed27 Date: Mon, 12 Feb 2024 21:18:54 +0200 Subject: [PATCH 01/11] Added doc file and ERD diagram image --- docs/prisma-erd.svg | 2 + docs/schema.md | 226 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 228 insertions(+) create mode 100644 docs/prisma-erd.svg create mode 100644 docs/schema.md diff --git a/docs/prisma-erd.svg b/docs/prisma-erd.svg new file mode 100644 index 0000000..eb59b41 --- /dev/null +++ b/docs/prisma-erd.svg @@ -0,0 +1,2 @@ + +UserIntidStringemailStringnameStringpasswordUserRoleroleTeamIntidStringnameTeamMembersMemberRoleroleProjectIntidStringnameStringuuidDatabaseStringidStringnameDateTimecreatedAtIntstoragestatusstatusteamuserteamproject \ No newline at end of file diff --git a/docs/schema.md b/docs/schema.md new file mode 100644 index 0000000..91c3ee8 --- /dev/null +++ b/docs/schema.md @@ -0,0 +1,226 @@ +# Prisma Schema Documentation + +## Overview + +This is a a comprehensive overview of the database schema used in the platform, including entities, relationships, enums, diagrams and additional notes for proper usage and maintenance. + +### Technologies Used + +**Database Management System:** PostgreSQL + +- PostgreSQL is scalable open-source relational database management system known for SQL compliance, + advanced concurrency control, extensibility, and strong security. + +**ORM:** Prisma + +- Prisma is a Node.js and Typescript ORM with an intuitive data model, automated migrations, type-safety, + and auto-completion. It defines the main configuration of the data models in a file called `schema.prisma` + +## Schema Description + +### Generator + +Specifies how Prisma generates client code for interacting with the database, such as Prisma Client. + +```prisma +generator client { + provider = "prisma-client-js" +} +``` + +### Data Source + +Defines the database connection details, including the provider (in our case PostgreSQL), and URL, which is stored in an environment variable. + +```prisma +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} +``` + +### Entities + +#### Structure + +The schema consists of the following entities: + +- **User:** Represents users of the system. + + ```prisma + model User { + id Int @id @default(autoincrement()) + email String @unique + name String + password String + role UserRole @default(USER) + teams TeamMembers[] + } + ``` + +- **Team:** Represents teams with members and projects. + + ```prisma + model Team { + id Int @id @default(autoincrement()) + name String + members TeamMembers[] + projects Project[] + } + ``` + +- **TeamMembers:** Represents the relationship between users and teams, including their roles. + + ```prisma + model TeamMembers { + teamId Int + userId Int + team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + role MemberRole @default(MEMBER) + + @@id([teamId, userId]) + } + ``` + +- **Project:** Represents projects belonging to teams. + + ```prisma + model Project { + id Int @id @default(autoincrement()) + name String + teamId Int + team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) + databases Database[] + uuid String @default(uuid()) + } + ``` + +- **Database:** Represents databases associated with projects. + + ```prisma + model Database { + id String @id @default(uuid()) + name String + createdAt DateTime @default(now()) + storage Int + projectId Int + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) + status status @default(INACTIVE) + } + ``` + +#### Entity Details + +##### User + +- `id`: Auto-incremented unique identifier for the user. +- `email`: Unique email address of the user. +- `name`: Name of the user +- `password`: Hashed password of the user +- `role`: Role of the user, default: `USER` +- Relationships: + `teams` Many-to-many relationship with Team entity through TeamMembers property + +###### Suggestions + +- implementing a unique constraint on the email column +- Adding an image column for profile pictures of users +- Adding a column to store the user's preferred language and another for his preferred timezone + +##### Team + +- `id` Auto-incremented unique identifier for the team +- `name` Name of the team +- Relationships: + `members` One-to-many relationship with team members + `projects` One-to-many relationship with projects + +###### Suggestions + +- Adding a column to store a description of the team and its goals + +##### TeamMembers + +- `teamId` Foreign key referencing the id column of the Team table, pointing to the team that the member belongs to +- `userId` Foreign key referencing the id column of the User table, pointing to the user that is a member of the team +- `role` Role of the member in the team, default: `MEMBER` +- Relationships: + `team` Many-to-one relationship with teams + `user` Many-to-one relationship with users + +##### Project + +- `id` Auto-incremented unique identifier for the project +- `name` Name of the project +- `teamId` Foreign key referencing the id column of the Team table, pointing to the team that created the project +- `uuid` Unique UUID identifier for the project +- Relationships: + `team` Many-to-one relationship with the team that creates the projects + `databases` One-to-many relationship with databases associated with the project + +###### Suggestions + +- Adding a column to store a description of the project +- Combining the `id` and `uuid` columns in a single column `id` that uses a uuid to identify the project + +##### Database + +- `id` Unique UUID identifier for the database +- `name`Name of the database +- `createdAt` Timestamp indicating the creation time of the database +- `storage` Storage capacity of the database +- `projectId` Foreign key referencing the id column of the Project table, pointing to the project that the database belongs to +- Relationships: + `project` Many-to-one relationship with the project + +### Enums + +#### UserRole + +- `USER`: Regular user + - Regular user with standard access privileges on teams and projects. +- `ADMIN`: Admin user + - User with elevated access rights for system administration of the platform. + +```prisma +enum UserRole { + USER + ADMIN +} +``` + +#### MemberRole + +- `MEMBER`: Regular member + - Regular team member with access to the team's projects +- `LEADER`: Leader member + - The user that created the team and has the privileges to create and delete projects. + +```prisma +enum MemberRole { + LEADER + MEMBER +} +``` + +#### Status + +- `ACTIVE`: Active database + - Database that is online and fully responsive +- `INACTIVE`: Inactive database + - Database that hasn't been used for a while but still responsive +- `SLEEP`: Sleeping database + - Database that was `INACTIVE` for a certain amount of time and entered an offline mode, and it must be activated in order to be responsive for queries + +```prisma +enum status { + ACTIVE + INACTIVE + SLEEP +} +``` + +### ERD Diagram + +![1707762267097](./prisma-erd.svg) From 493aa943f253c6eb71204b006bc8c9bcca9db445 Mon Sep 17 00:00:00 2001 From: AmrMohamed27 Date: Mon, 12 Feb 2024 21:23:19 +0200 Subject: [PATCH 02/11] Fixed relationships not showing as a list --- docs/schema.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/schema.md b/docs/schema.md index 91c3ee8..3e7bb23 100644 --- a/docs/schema.md +++ b/docs/schema.md @@ -120,7 +120,7 @@ The schema consists of the following entities: - `password`: Hashed password of the user - `role`: Role of the user, default: `USER` - Relationships: - `teams` Many-to-many relationship with Team entity through TeamMembers property + - `teams` Many-to-many relationship with Team entity through TeamMembers property ###### Suggestions @@ -133,8 +133,8 @@ The schema consists of the following entities: - `id` Auto-incremented unique identifier for the team - `name` Name of the team - Relationships: - `members` One-to-many relationship with team members - `projects` One-to-many relationship with projects + - `members` One-to-many relationship with team members + - `projects` One-to-many relationship with projects ###### Suggestions @@ -146,8 +146,8 @@ The schema consists of the following entities: - `userId` Foreign key referencing the id column of the User table, pointing to the user that is a member of the team - `role` Role of the member in the team, default: `MEMBER` - Relationships: - `team` Many-to-one relationship with teams - `user` Many-to-one relationship with users + - `team` Many-to-one relationship with teams + - `user` Many-to-one relationship with users ##### Project @@ -156,8 +156,8 @@ The schema consists of the following entities: - `teamId` Foreign key referencing the id column of the Team table, pointing to the team that created the project - `uuid` Unique UUID identifier for the project - Relationships: - `team` Many-to-one relationship with the team that creates the projects - `databases` One-to-many relationship with databases associated with the project + - `team` Many-to-one relationship with the team that creates the projects + - `databases` One-to-many relationship with databases associated with the project ###### Suggestions @@ -172,7 +172,7 @@ The schema consists of the following entities: - `storage` Storage capacity of the database - `projectId` Foreign key referencing the id column of the Project table, pointing to the project that the database belongs to - Relationships: - `project` Many-to-one relationship with the project + - `project` Many-to-one relationship with the project ### Enums From 3e841f00849bf2b0be26d8871affc79db2c785aa Mon Sep 17 00:00:00 2001 From: AmrMohamed27 Date: Wed, 14 Feb 2024 16:37:37 +0200 Subject: [PATCH 03/11] Added .vscode folder to gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index c6bba59..84bf5a6 100644 --- a/.gitignore +++ b/.gitignore @@ -122,6 +122,9 @@ dist # Stores VSCode versions used for testing VSCode extensions .vscode-test +# VSCode +.vscode/ + # yarn v2 .yarn/cache .yarn/unplugged From a0585e0dbebdc967b1557cbe8cea0e5a56c6ab35 Mon Sep 17 00:00:00 2001 From: AmrMohamed27 Date: Wed, 14 Feb 2024 16:38:39 +0200 Subject: [PATCH 04/11] Removed suggestions and moved diagram to top --- docs/schema.md | 123 ++++++++++++++++++++++--------------------------- 1 file changed, 54 insertions(+), 69 deletions(-) diff --git a/docs/schema.md b/docs/schema.md index 3e7bb23..e0a1ded 100644 --- a/docs/schema.md +++ b/docs/schema.md @@ -16,6 +16,10 @@ This is a a comprehensive overview of the database schema used in the platform, - Prisma is a Node.js and Typescript ORM with an intuitive data model, automated migrations, type-safety, and auto-completion. It defines the main configuration of the data models in a file called `schema.prisma` +### ERD Diagram + +![ERD diagram of the schema](./prisma-erd.svg "ERD Diagram of the schema") + ## Schema Description ### Generator @@ -47,68 +51,68 @@ The schema consists of the following entities: - **User:** Represents users of the system. - ```prisma - model User { - id Int @id @default(autoincrement()) - email String @unique - name String - password String - role UserRole @default(USER) - teams TeamMembers[] - } - ``` +```prisma +model User { + id Int @id @default(autoincrement()) + email String @unique + name String + password String + role UserRole @default(USER) + teams TeamMembers[] +} +``` - **Team:** Represents teams with members and projects. - ```prisma - model Team { - id Int @id @default(autoincrement()) - name String - members TeamMembers[] - projects Project[] - } - ``` +```prisma +model Team { + id Int @id @default(autoincrement()) + name String + members TeamMembers[] + projects Project[] +} +``` - **TeamMembers:** Represents the relationship between users and teams, including their roles. - ```prisma - model TeamMembers { - teamId Int - userId Int - team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - role MemberRole @default(MEMBER) - - @@id([teamId, userId]) - } - ``` +```prisma +model TeamMembers { + teamId Int + userId Int + team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + role MemberRole @default(MEMBER) + + @@id([teamId, userId]) +} +``` - **Project:** Represents projects belonging to teams. - ```prisma - model Project { - id Int @id @default(autoincrement()) - name String - teamId Int - team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) - databases Database[] - uuid String @default(uuid()) - } - ``` +```prisma +model Project { + id Int @id @default(autoincrement()) + name String + teamId Int + team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) + databases Database[] + uuid String @default(uuid()) +} +``` - **Database:** Represents databases associated with projects. - ```prisma - model Database { - id String @id @default(uuid()) - name String - createdAt DateTime @default(now()) - storage Int - projectId Int - project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) - status status @default(INACTIVE) - } - ``` +```prisma +model Database { + id String @id @default(uuid()) + name String + createdAt DateTime @default(now()) + storage Int + projectId Int + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) + status status @default(INACTIVE) +} +``` #### Entity Details @@ -122,12 +126,6 @@ The schema consists of the following entities: - Relationships: - `teams` Many-to-many relationship with Team entity through TeamMembers property -###### Suggestions - -- implementing a unique constraint on the email column -- Adding an image column for profile pictures of users -- Adding a column to store the user's preferred language and another for his preferred timezone - ##### Team - `id` Auto-incremented unique identifier for the team @@ -136,10 +134,6 @@ The schema consists of the following entities: - `members` One-to-many relationship with team members - `projects` One-to-many relationship with projects -###### Suggestions - -- Adding a column to store a description of the team and its goals - ##### TeamMembers - `teamId` Foreign key referencing the id column of the Team table, pointing to the team that the member belongs to @@ -159,11 +153,6 @@ The schema consists of the following entities: - `team` Many-to-one relationship with the team that creates the projects - `databases` One-to-many relationship with databases associated with the project -###### Suggestions - -- Adding a column to store a description of the project -- Combining the `id` and `uuid` columns in a single column `id` that uses a uuid to identify the project - ##### Database - `id` Unique UUID identifier for the database @@ -220,7 +209,3 @@ enum status { SLEEP } ``` - -### ERD Diagram - -![1707762267097](./prisma-erd.svg) From 13d3d4dee65041604368c82e7155614f1e9c7b44 Mon Sep 17 00:00:00 2001 From: AmrMohamed27 Date: Thu, 15 Feb 2024 01:15:28 +0200 Subject: [PATCH 05/11] ignoring .gitignore file --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 84bf5a6..0d62657 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ yarn-error.log* lerna-debug.log* .pnpm-debug.log* +# Ignore file +.gitignore + # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json @@ -131,3 +134,4 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* + From 193c62cf021180f092d6d52b2936908e6ce5a103 Mon Sep 17 00:00:00 2001 From: AmrMohamed27 Date: Thu, 15 Feb 2024 01:27:23 +0200 Subject: [PATCH 06/11] Added Prisma Components section --- docs/schema.md | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/docs/schema.md b/docs/schema.md index e0a1ded..a6735cf 100644 --- a/docs/schema.md +++ b/docs/schema.md @@ -1,4 +1,4 @@ -# Prisma Schema Documentation +# Schema Documentation ## Overview @@ -16,11 +16,9 @@ This is a a comprehensive overview of the database schema used in the platform, - Prisma is a Node.js and Typescript ORM with an intuitive data model, automated migrations, type-safety, and auto-completion. It defines the main configuration of the data models in a file called `schema.prisma` -### ERD Diagram +## Prisma Components -![ERD diagram of the schema](./prisma-erd.svg "ERD Diagram of the schema") - -## Schema Description +We depend on Prisma as an ORM to connect our application to the database using the following components: ### Generator @@ -43,6 +41,8 @@ datasource db { } ``` +## Schema Description + ### Entities #### Structure @@ -114,9 +114,13 @@ model Database { } ``` -#### Entity Details +### ERD Diagram + +![ERD diagram of the schema](./prisma-erd.svg "ERD Diagram of the schema") + +### Entity Details -##### User +#### User - `id`: Auto-incremented unique identifier for the user. - `email`: Unique email address of the user. @@ -126,7 +130,7 @@ model Database { - Relationships: - `teams` Many-to-many relationship with Team entity through TeamMembers property -##### Team +#### Team - `id` Auto-incremented unique identifier for the team - `name` Name of the team @@ -134,7 +138,7 @@ model Database { - `members` One-to-many relationship with team members - `projects` One-to-many relationship with projects -##### TeamMembers +#### TeamMembers - `teamId` Foreign key referencing the id column of the Team table, pointing to the team that the member belongs to - `userId` Foreign key referencing the id column of the User table, pointing to the user that is a member of the team @@ -143,7 +147,7 @@ model Database { - `team` Many-to-one relationship with teams - `user` Many-to-one relationship with users -##### Project +#### Project - `id` Auto-incremented unique identifier for the project - `name` Name of the project @@ -153,7 +157,7 @@ model Database { - `team` Many-to-one relationship with the team that creates the projects - `databases` One-to-many relationship with databases associated with the project -##### Database +#### Database - `id` Unique UUID identifier for the database - `name`Name of the database From 80630fc2ddddec2e0974816aca16eca7054cf377 Mon Sep 17 00:00:00 2001 From: AmrMohamed27 Date: Thu, 15 Feb 2024 01:28:53 +0200 Subject: [PATCH 07/11] Added a caption above the ERD diagram --- docs/schema.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/schema.md b/docs/schema.md index a6735cf..ee1265d 100644 --- a/docs/schema.md +++ b/docs/schema.md @@ -116,6 +116,7 @@ model Database { ### ERD Diagram +This ERD Diagram helps visualize the relationships between each entity in the schema and how they depend on each other ![ERD diagram of the schema](./prisma-erd.svg "ERD Diagram of the schema") ### Entity Details From 2d4bef655fa5ff4fe4e056e619127873ef9993fa Mon Sep 17 00:00:00 2001 From: Salma-Mahmoudd Date: Thu, 15 Feb 2024 21:24:45 +0200 Subject: [PATCH 08/11] Added teamController file, modified endpoints and join function handled with some database modifications --- package-lock.json | 62 +++---- package.json | 3 +- .../20240207163006_uuid/migration.sql | 8 - .../migration.sql | 6 + prisma/schema.prisma | 2 + requests.rest | 130 +++++++++++++++ src/app.js | 10 +- src/controllers/teamController.js | 106 ++++++++++++ src/models/teamModel.js | 156 ++++++++++-------- src/routes/teamRoute.js | 21 +++ 10 files changed, 393 insertions(+), 111 deletions(-) delete mode 100644 prisma/migrations/20240207163006_uuid/migration.sql rename prisma/migrations/{20240205003520_init => 20240215185513_init}/migration.sql (93%) create mode 100644 src/controllers/teamController.js create mode 100644 src/routes/teamRoute.js diff --git a/package-lock.json b/package-lock.json index 4f5c532..f08e2bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,9 +15,10 @@ "cookie-parser": "^1.4.6", "dotenv": "^16.4.1", "express": "^4.18.2", + "express-async-handler": "^1.2.0", "jsonwebtoken": "^9.0.2", "nodemon": "^3.0.3", - "prisma": "^5.8.1" + "prisma": "^5.9.1" } }, "node_modules/@mapbox/node-pre-gyp": { @@ -71,43 +72,43 @@ } }, "node_modules/@prisma/debug": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.8.1.tgz", - "integrity": "sha512-tjuw7eA0Us3T42jx9AmAgL58rzwzpFGYc3R7Y4Ip75EBYrKMBA1YihuWMcBC92ILmjlQ/u3p8VxcIE0hr+fZfg==" + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.9.1.tgz", + "integrity": "sha512-yAHFSFCg8KVoL0oRUno3m60GAjsUKYUDkQ+9BA2X2JfVR3kRVSJFc/GpQ2fSORi4pSHZR9orfM4UC9OVXIFFTA==" }, "node_modules/@prisma/engines": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.8.1.tgz", - "integrity": "sha512-TJgYLRrZr56uhqcXO4GmP5be+zjCIHtLDK20Cnfg+o9d905hsN065QOL+3Z0zQAy6YD31Ol4u2kzSfRmbJv/uA==", + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.9.1.tgz", + "integrity": "sha512-gkdXmjxQ5jktxWNdDA5aZZ6R8rH74JkoKq6LD5mACSvxd2vbqWeWIOV0Py5wFC8vofOYShbt6XUeCIUmrOzOnQ==", "hasInstallScript": true, "dependencies": { - "@prisma/debug": "5.8.1", - "@prisma/engines-version": "5.8.1-1.78caf6feeaed953168c64e15a249c3e9a033ebe2", - "@prisma/fetch-engine": "5.8.1", - "@prisma/get-platform": "5.8.1" + "@prisma/debug": "5.9.1", + "@prisma/engines-version": "5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64", + "@prisma/fetch-engine": "5.9.1", + "@prisma/get-platform": "5.9.1" } }, "node_modules/@prisma/engines-version": { - "version": "5.8.1-1.78caf6feeaed953168c64e15a249c3e9a033ebe2", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.8.1-1.78caf6feeaed953168c64e15a249c3e9a033ebe2.tgz", - "integrity": "sha512-f5C3JM3l9yhGr3cr4FMqWloFaSCpNpMi58Om22rjD2DOz3owci2mFdFXMgnAGazFPKrCbbEhcxdsRfspEYRoFQ==" + "version": "5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64.tgz", + "integrity": "sha512-HFl7275yF0FWbdcNvcSRbbu9JCBSLMcurYwvWc8WGDnpu7APxQo2ONtZrUggU3WxLxUJ2uBX+0GOFIcJeVeOOQ==" }, "node_modules/@prisma/fetch-engine": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.8.1.tgz", - "integrity": "sha512-+bgjjoSFa6uYEbAPlklfoVSStOEfcpheOjoBoNsNNSQdSzcwE2nM4Q0prun0+P8/0sCHo18JZ9xqa8gObvgOUw==", + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.9.1.tgz", + "integrity": "sha512-l0goQOMcNVOJs1kAcwqpKq3ylvkD9F04Ioe1oJoCqmz05mw22bNAKKGWuDd3zTUoUZr97va0c/UfLNru+PDmNA==", "dependencies": { - "@prisma/debug": "5.8.1", - "@prisma/engines-version": "5.8.1-1.78caf6feeaed953168c64e15a249c3e9a033ebe2", - "@prisma/get-platform": "5.8.1" + "@prisma/debug": "5.9.1", + "@prisma/engines-version": "5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64", + "@prisma/get-platform": "5.9.1" } }, "node_modules/@prisma/get-platform": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.8.1.tgz", - "integrity": "sha512-wnA+6HTFcY+tkykMokix9GiAkaauPC5W/gg0O5JB0J8tCTNWrqpnQ7AsaGRfkYUbeOIioh6woDjQrGTTRf1Zag==", + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.9.1.tgz", + "integrity": "sha512-6OQsNxTyhvG+T2Ksr8FPFpuPeL4r9u0JF0OZHUBI/Uy9SS43sPyAIutt4ZEAyqWQt104ERh70EZedkHZKsnNbg==", "dependencies": { - "@prisma/debug": "5.8.1" + "@prisma/debug": "5.9.1" } }, "node_modules/abbrev": { @@ -553,6 +554,11 @@ "node": ">= 0.10.0" } }, + "node_modules/express-async-handler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/express-async-handler/-/express-async-handler-1.2.0.tgz", + "integrity": "sha512-rCSVtPXRmQSW8rmik/AIb2P0op6l7r1fMW538yyvTMltCO4xQEWMmobfrIxN2V1/mVrgxB8Az3reYF6yUZw37w==" + }, "node_modules/express/node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -1337,12 +1343,12 @@ } }, "node_modules/prisma": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.8.1.tgz", - "integrity": "sha512-N6CpjzECnUHZ5beeYpDzkt2rYpEdAeqXX2dweu6BoQaeYkNZrC/WJHM+5MO/uidFHTak8QhkPKBWck1o/4MD4A==", + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.9.1.tgz", + "integrity": "sha512-Hy/8KJZz0ELtkw4FnG9MS9rNWlXcJhf98Z2QMqi0QiVMoS8PzsBkpla0/Y5hTlob8F3HeECYphBjqmBxrluUrQ==", "hasInstallScript": true, "dependencies": { - "@prisma/engines": "5.8.1" + "@prisma/engines": "5.9.1" }, "bin": { "prisma": "build/index.js" diff --git a/package.json b/package.json index 4bba0b6..1f65ba7 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,9 @@ "cookie-parser": "^1.4.6", "dotenv": "^16.4.1", "express": "^4.18.2", + "express-async-handler": "^1.2.0", "jsonwebtoken": "^9.0.2", "nodemon": "^3.0.3", - "prisma": "^5.8.1" + "prisma": "^5.9.1" } } diff --git a/prisma/migrations/20240207163006_uuid/migration.sql b/prisma/migrations/20240207163006_uuid/migration.sql deleted file mode 100644 index 9d6acd1..0000000 --- a/prisma/migrations/20240207163006_uuid/migration.sql +++ /dev/null @@ -1,8 +0,0 @@ -/* - Warnings: - - - The required column `uuid` was added to the `Project` table with a prisma-level default value. This is not possible if the table is not empty. Please add this column as optional, then populate it before making it required. - -*/ --- AlterTable -ALTER TABLE "Project" ADD COLUMN "uuid" TEXT NOT NULL; diff --git a/prisma/migrations/20240205003520_init/migration.sql b/prisma/migrations/20240215185513_init/migration.sql similarity index 93% rename from prisma/migrations/20240205003520_init/migration.sql rename to prisma/migrations/20240215185513_init/migration.sql index 65d9ce7..e4b8487 100644 --- a/prisma/migrations/20240205003520_init/migration.sql +++ b/prisma/migrations/20240215185513_init/migration.sql @@ -22,6 +22,8 @@ CREATE TABLE "User" ( CREATE TABLE "Team" ( "id" SERIAL NOT NULL, "name" TEXT NOT NULL, + "token" TEXT, + "expiry" TIMESTAMP(3), CONSTRAINT "Team_pkey" PRIMARY KEY ("id") ); @@ -40,6 +42,7 @@ CREATE TABLE "Project" ( "id" SERIAL NOT NULL, "name" TEXT NOT NULL, "teamId" INTEGER NOT NULL, + "uuid" TEXT NOT NULL, CONSTRAINT "Project_pkey" PRIMARY KEY ("id") ); @@ -59,6 +62,9 @@ CREATE TABLE "Database" ( -- CreateIndex CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); +-- CreateIndex +CREATE UNIQUE INDEX "Team_token_key" ON "Team"("token"); + -- AddForeignKey ALTER TABLE "TeamMembers" ADD CONSTRAINT "TeamMembers_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0cc89c6..35c3a7c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -19,6 +19,8 @@ model User { model Team { id Int @id @default(autoincrement()) name String + token String? @unique + expiry DateTime? members TeamMembers[] projects Project[] } diff --git a/requests.rest b/requests.rest index 9dd4c0a..61a1abb 100644 --- a/requests.rest +++ b/requests.rest @@ -58,4 +58,134 @@ Content-Type: application/json { "teamId": 1 +} + + +//______________________________________________________ + +### +// Register user + +POST http://localhost:3000/register +Content-Type: application/json + +{ + "email": "s@g.com", + "username": "salma", + "password": "1234" +} + +### +// Login user + +POST http://localhost:3000/login +Content-Type: application/json + +{ + "email": "s@g.com", + "password": "1234" +} + +### +// Create team + +POST http://localhost:3000/team +Content-Type: application/json + +{ + "teamName": "team5", + "leaderId": 1 +} + +### +// Add member to team + +PUT http://localhost:3000/team/member +Content-Type: application/json + +{ + "memberEmail": "s@g.com", + "teamId": 5, + "userId": 1 +} + +### +// Get code of team + +PUT http://localhost:3000/team/code +Content-Type: application/json + +{ + "teamId": 1, + "userId": 2 +} + + +### +// Join a team + +PUT http://localhost:3000/team/join +Content-Type: application/json + +{ + "token": "eaff7ffa", + "userId": 1 +} + + +### +// Update team + +PUT http://localhost:3000/team +Content-Type: application/json + +{ + "name": "sssss", + "teamId": 2, + "userId": 2 +} + +### +// Get all users of the team + +GET http://localhost:3000/team/users +Content-Type: application/json + +{ + "teamId": 5, + "userId": 1 +} + +### +// List all teams of the user + +GET http://localhost:3000/team +Content-Type: application/json + +{ + "email": "s@g.com" +} + +### +// Delete member from team + +DELETE http://localhost:3000/team/member +Content-Type: application/json + +{ + "memberEmail": "amr11@g.com", + "teamId": 1, + "userId": 2 +} + + +### +// Delete team + +DELETE http://localhost:3000/team +Content-Type: application/json + +{ + "teamId": 3, + "userId": 2 } \ No newline at end of file diff --git a/src/app.js b/src/app.js index 7fbd987..0eeb346 100644 --- a/src/app.js +++ b/src/app.js @@ -1,16 +1,15 @@ import express from "express"; -import bodyParser from "body-parser"; import userRoute from "./routes/userRoute.js"; import cookieParser from "cookie-parser"; import { tokenAuth } from "./middlewares/authMiddleware.js"; import projectRoute from "./routes/projectRoute.js"; +import teamRoute from "./routes/teamRoute.js"; const app = express(); const port = 3000; app.use(express.json()); -app.use(bodyParser.json()); -app.use(bodyParser.urlencoded({ extended: true })); +app.use(express.urlencoded({ extended: true })); app.use(cookieParser()); app.use("/", userRoute); @@ -19,7 +18,12 @@ app.get("/", tokenAuth, (req, res) => { }); app.use("/project", projectRoute); +app.use("/team", teamRoute); app.listen(port, () => { console.log(`Server is running on port ${port}`); }); + +app.use((err, req, res, next) => { + res.status(500).json({ message: "Internal server error" }); +}); diff --git a/src/controllers/teamController.js b/src/controllers/teamController.js new file mode 100644 index 0000000..fba3092 --- /dev/null +++ b/src/controllers/teamController.js @@ -0,0 +1,106 @@ +import * as teamModel from "../models/teamModel.js"; +import { getUser } from "../models/userModel.js"; +import asyncHandler from "express-async-handler"; + +export async function isLeader(teamId, userId) { + const user = await teamModel.memberExist(teamId, userId); + if (user.role === "LEADER") { + return true; + } + return false; +} + +export const createTeam = asyncHandler(async (req, res) => { + const { teamName, leaderId } = req.body; + const newTeam = await teamModel.createTeam(teamName, leaderId); + res.status(201).json({ message: "Team created successfully" }); +}); + +export const addMember = asyncHandler(async (req, res) => { + const { memberEmail, teamId, userId } = req.body; + if (await isLeader(teamId, userId)) { + const addedMember = await teamModel.addMember(memberEmail, teamId); + if (addedMember) { + res.status(201).json({ message: "Member added successfully" }); + } else { + res.status(409).json({ message: "Member already exists in the team" }); + } + } else { + res.status(401).json({ message: "Unauthorized: user is not LEADER" }); + } +}); + +export const deleteMember = asyncHandler(async (req, res) => { + const { memberEmail, teamId, userId } = req.body; + if (await isLeader(teamId, userId)) { + const deletedMember = await teamModel.deleteMember(memberEmail, teamId); + if (deletedMember) { + res.status(201).json({ message: "Member deleted successfully" }); + } else { + res.status(409).json({ message: "Member does not exist in the team" }); + } + } else { + res.status(401).json({ message: "Unauthorized: user is not LEADER" }); + } +}); + +export const updateTeam = asyncHandler(async (req, res) => { + const { newName, teamId, userId } = req.body; + if (await isLeader(teamId, userId)) { + await teamModel.updateTeam(newName, teamId); + res.status(201).json({ message: "Team updated successfully" }); + } else { + res.status(401).json({ message: "Unauthorized: user is not LEADER" }); + } +}); + +export const deleteTeam = asyncHandler(async (req, res) => { + const { teamId, userId } = req.body; + if (await isLeader(teamId, userId)) { + await teamModel.deleteTeam(teamId); + res.status(201).json({ message: "Team deleted successfully" }); + } else { + res.status(401).json({ message: "Unauthorized: user is not LEADER" }); + } +}); + +export const getUsersInTeam = asyncHandler(async (req, res) => { + const teamId = req.body.teamId; + const team = await teamModel.getTeam(teamId); + res.status(200).json(team[0].members.map((member) => member.user.email)); +}); + +export const allTeamsOfUser = asyncHandler(async (req, res) => { + const email = req.body.email; + const user = await getUser(email); + if (user.teams) { + res.status(200).json( + user.teams.map((team) => ({ + role: team.role, + teamName: team.team.name, + })) + ); + } else { + res.status(409).json({ message: "No teams found" }); + } +}); + +export const teamToken = asyncHandler(async (req, res) => { + const { teamId, userId } = req.body; + if (await isLeader(teamId, userId)) { + const token = await teamModel.tokenForTeam(teamId); + res.status(200).json({ Code: token }); + } else { + res.status(401).json({ message: "Unauthorized: user is not LEADER" }); + } +}); + +export const joinTeam = asyncHandler(async (req, res) => { + const { token, userId } = req.body; + const joinedTeam = await teamModel.joinTeam(token, userId); + if (joinedTeam) { + res.status(201).json({ message: "Joined team successfully" }); + } else { + res.status(402).json({ message: "Invalid code" }); + } +}); diff --git a/src/models/teamModel.js b/src/models/teamModel.js index 1b376a5..ee2195b 100644 --- a/src/models/teamModel.js +++ b/src/models/teamModel.js @@ -1,98 +1,112 @@ -import { prisma } from "../utils/db.js" -import { getUserId } from "./userModel.js" -export async function createTeam(leader_id, Name) { +import { prisma } from "../utils/db.js"; +import { getUserId } from "./userModel.js"; +import crypto from "crypto"; + +export async function memberExist(teamId, userId) { + const exist = await prisma.teamMembers.findUnique({ + where: { + teamId_userId: { + teamId: teamId, + userId: userId, + }, + }, + }); + return exist; +} + +export async function createTeam(teamName, leaderId) { const team = await prisma.team.create({ data: { - name: Name, + name: teamName, members: { create: [ { role: "LEADER", - user: { - connect: { id: leader_id }, - }, + user: { connect: { id: leaderId } }, }, ], }, }, - }) - return team -} -export async function memberExist(team_id, user_id) { - const exist = await prisma.teamMembers.findUnique({ - where: { - teamId_userId: { - teamId: team_id, - userId: user_id, - }, - }, - }) - return exist + }); + return team; } -export async function addMember(team_id, memberEmail) { - const user_id = await getUserId(memberEmail) - if (!user_id) { - return false - } - if (!(await memberExist(team_id, user_id.id))) { + +export async function addMember(memberEmail, teamId) { + const member = await getUserId(memberEmail); + if (!(await memberExist(teamId, member.id))) { await prisma.teamMembers.create({ data: { - user: { - connect: { id: user_id.id }, - }, - team: { - connect: { id: team_id }, - }, + user: { connect: { id: member.id } }, + team: { connect: { id: teamId } }, }, - }) - return true - } else { - return false + }); + return true; } + return false; } -export async function deleteMember(team_id, e_mail) { - const user = await getUserId(e_mail) - if (!user) { - return false - } - await prisma.teamMembers.delete({ - where: { - teamId_userId: { - teamId: team_id, - userId: user.id, +export async function deleteMember(memberEmail, teamId) { + const member = await getUserId(memberEmail); + if (await memberExist(teamId, member.id)) { + await prisma.teamMembers.delete({ + where: { + teamId_userId: { + teamId: teamId, + userId: member.id, + }, }, - }, - }) - return true + }); + return true; + } + return false; } -export async function getTeam(team_id) { - const team = await prisma.team.findUnique({ - where: { - id: team_id, - }, - include: { members: { include: { user: true } } }, - }) - return team +export async function getTeam(teamId) { + const team = await prisma.team.findMany({ + where: { id: teamId }, + select: { members: { select: { user: true } } }, + }); + return team; } -export async function updateTeam(team_id, Name) { +export async function updateTeam(newName, teamId) { const team = await prisma.team.update({ - where: { - id: team_id, - }, - data: { - name: Name, - }, - }) - return team + where: { id: teamId }, + data: { name: newName }, + }); + return team; } -export async function deleteTeam(team_id) { +export async function deleteTeam(teamId) { await prisma.team.delete({ - where: { - id: team_id, - }, - }) + where: { id: teamId }, + }); +} + +export async function tokenForTeam(teamId) { + let token; + do { + token = crypto.randomBytes(8).toString("hex").substring(0, 8); + } while (await prisma.team.findUnique({ where: { token } })); + await prisma.team.update({ + where: { id: teamId }, + data: { token: token, expiry: new Date(Date.now() + 24 * 60 * 60 * 1000) }, + }); + return token; +} + +export async function joinTeam(token, userId) { + const team = await prisma.team.findFirst({ + where: { token: token, expiry: { gt: new Date() } }, + }); + if (team) { + await prisma.teamMembers.create({ + data: { + user: { connect: { id: userId } }, + team: { connect: { id: team.id } }, + }, + }); + return true; + } + return false; } diff --git a/src/routes/teamRoute.js b/src/routes/teamRoute.js new file mode 100644 index 0000000..2958ce0 --- /dev/null +++ b/src/routes/teamRoute.js @@ -0,0 +1,21 @@ +import express from "express"; +import { tokenAuth } from "../middlewares/authMiddleware.js"; +import * as teamController from "../controllers/teamController.js"; + +const router = express.Router(); + +router + .route("/") + .get(tokenAuth, teamController.allTeamsOfUser) + .post(tokenAuth, teamController.createTeam) + .delete(tokenAuth, teamController.deleteTeam) + .put(tokenAuth, teamController.updateTeam); +router + .route("/member") + .put(tokenAuth, teamController.addMember) + .delete(tokenAuth, teamController.deleteMember); +router.get("/users", tokenAuth, teamController.getUsersInTeam); +router.put("/join", tokenAuth, teamController.joinTeam); +router.put("/code", tokenAuth, teamController.teamToken); + +export default router; From 903350c4f75eb3737a56a150c6b30e8bc125abdd Mon Sep 17 00:00:00 2001 From: Salma-Mahmoudd Date: Fri, 16 Feb 2024 19:48:44 +0200 Subject: [PATCH 09/11] Using the reg.user --- src/controllers/teamController.js | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/controllers/teamController.js b/src/controllers/teamController.js index fba3092..b4b8534 100644 --- a/src/controllers/teamController.js +++ b/src/controllers/teamController.js @@ -11,13 +11,15 @@ export async function isLeader(teamId, userId) { } export const createTeam = asyncHandler(async (req, res) => { - const { teamName, leaderId } = req.body; + const { teamName } = req.body; + const leaderId = req.user.id; const newTeam = await teamModel.createTeam(teamName, leaderId); res.status(201).json({ message: "Team created successfully" }); }); export const addMember = asyncHandler(async (req, res) => { - const { memberEmail, teamId, userId } = req.body; + const { memberEmail, teamId } = req.body; + const userId = req.user.id; if (await isLeader(teamId, userId)) { const addedMember = await teamModel.addMember(memberEmail, teamId); if (addedMember) { @@ -31,7 +33,8 @@ export const addMember = asyncHandler(async (req, res) => { }); export const deleteMember = asyncHandler(async (req, res) => { - const { memberEmail, teamId, userId } = req.body; + const { memberEmail, teamId } = req.body; + const userId = req.user.id; if (await isLeader(teamId, userId)) { const deletedMember = await teamModel.deleteMember(memberEmail, teamId); if (deletedMember) { @@ -45,7 +48,8 @@ export const deleteMember = asyncHandler(async (req, res) => { }); export const updateTeam = asyncHandler(async (req, res) => { - const { newName, teamId, userId } = req.body; + const { newName, teamId } = req.body; + const userId = req.user.id; if (await isLeader(teamId, userId)) { await teamModel.updateTeam(newName, teamId); res.status(201).json({ message: "Team updated successfully" }); @@ -55,7 +59,8 @@ export const updateTeam = asyncHandler(async (req, res) => { }); export const deleteTeam = asyncHandler(async (req, res) => { - const { teamId, userId } = req.body; + const { teamId } = req.body; + const userId = req.user.id; if (await isLeader(teamId, userId)) { await teamModel.deleteTeam(teamId); res.status(201).json({ message: "Team deleted successfully" }); @@ -65,13 +70,13 @@ export const deleteTeam = asyncHandler(async (req, res) => { }); export const getUsersInTeam = asyncHandler(async (req, res) => { - const teamId = req.body.teamId; + const { teamId } = req.body; const team = await teamModel.getTeam(teamId); res.status(200).json(team[0].members.map((member) => member.user.email)); }); export const allTeamsOfUser = asyncHandler(async (req, res) => { - const email = req.body.email; + const email = req.user.email; const user = await getUser(email); if (user.teams) { res.status(200).json( @@ -86,7 +91,8 @@ export const allTeamsOfUser = asyncHandler(async (req, res) => { }); export const teamToken = asyncHandler(async (req, res) => { - const { teamId, userId } = req.body; + const { teamId } = req.body; + const userId = req.user.id; if (await isLeader(teamId, userId)) { const token = await teamModel.tokenForTeam(teamId); res.status(200).json({ Code: token }); @@ -96,7 +102,8 @@ export const teamToken = asyncHandler(async (req, res) => { }); export const joinTeam = asyncHandler(async (req, res) => { - const { token, userId } = req.body; + const { token } = req.body; + const userId = req.user.id; const joinedTeam = await teamModel.joinTeam(token, userId); if (joinedTeam) { res.status(201).json({ message: "Joined team successfully" }); From 3f74eee45127bb673fcaaffe7c56851a296bc7c5 Mon Sep 17 00:00:00 2001 From: Salma-Mahmoudd Date: Fri, 16 Feb 2024 21:11:48 +0200 Subject: [PATCH 10/11] For testing team controller --- requests.rest | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/requests.rest b/requests.rest index 61a1abb..05b6638 100644 --- a/requests.rest +++ b/requests.rest @@ -93,8 +93,7 @@ POST http://localhost:3000/team Content-Type: application/json { - "teamName": "team5", - "leaderId": 1 + "teamName": "team9" } ### @@ -104,9 +103,8 @@ PUT http://localhost:3000/team/member Content-Type: application/json { - "memberEmail": "s@g.com", - "teamId": 5, - "userId": 1 + "memberEmail": "amr11@g.com", + "teamId": 5 } ### @@ -116,8 +114,7 @@ PUT http://localhost:3000/team/code Content-Type: application/json { - "teamId": 1, - "userId": 2 + "teamId": 9 } @@ -128,8 +125,7 @@ PUT http://localhost:3000/team/join Content-Type: application/json { - "token": "eaff7ffa", - "userId": 1 + "token": "2c1ce63e" } @@ -140,9 +136,8 @@ PUT http://localhost:3000/team Content-Type: application/json { - "name": "sssss", - "teamId": 2, - "userId": 2 + "newName": "sssss", + "teamId": 9 } ### @@ -152,8 +147,7 @@ GET http://localhost:3000/team/users Content-Type: application/json { - "teamId": 5, - "userId": 1 + "teamId": 9 } ### @@ -162,9 +156,7 @@ Content-Type: application/json GET http://localhost:3000/team Content-Type: application/json -{ - "email": "s@g.com" -} + ### // Delete member from team @@ -174,8 +166,7 @@ Content-Type: application/json { "memberEmail": "amr11@g.com", - "teamId": 1, - "userId": 2 + "teamId": 4 } @@ -186,6 +177,5 @@ DELETE http://localhost:3000/team Content-Type: application/json { - "teamId": 3, - "userId": 2 + "teamId": 8 } \ No newline at end of file From 6769c9fa563afdfdc6c4a72cc7242cac4a3720f5 Mon Sep 17 00:00:00 2001 From: Salma-Mahmoudd Date: Sat, 17 Feb 2024 03:08:05 +0200 Subject: [PATCH 11/11] Edit team route --- requests.rest | 4 ++-- src/controllers/teamController.js | 2 +- src/routes/teamRoute.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requests.rest b/requests.rest index 05b6638..a461380 100644 --- a/requests.rest +++ b/requests.rest @@ -143,11 +143,11 @@ Content-Type: application/json ### // Get all users of the team -GET http://localhost:3000/team/users +GET http://localhost:3000/team/member Content-Type: application/json { - "teamId": 9 + "teamId": 7 } ### diff --git a/src/controllers/teamController.js b/src/controllers/teamController.js index b4b8534..5b6eb6e 100644 --- a/src/controllers/teamController.js +++ b/src/controllers/teamController.js @@ -69,7 +69,7 @@ export const deleteTeam = asyncHandler(async (req, res) => { } }); -export const getUsersInTeam = asyncHandler(async (req, res) => { +export const getMembers = asyncHandler(async (req, res) => { const { teamId } = req.body; const team = await teamModel.getTeam(teamId); res.status(200).json(team[0].members.map((member) => member.user.email)); diff --git a/src/routes/teamRoute.js b/src/routes/teamRoute.js index 2958ce0..3a0aba3 100644 --- a/src/routes/teamRoute.js +++ b/src/routes/teamRoute.js @@ -12,9 +12,9 @@ router .put(tokenAuth, teamController.updateTeam); router .route("/member") + .get(tokenAuth, teamController.getMembers) .put(tokenAuth, teamController.addMember) .delete(tokenAuth, teamController.deleteMember); -router.get("/users", tokenAuth, teamController.getUsersInTeam); router.put("/join", tokenAuth, teamController.joinTeam); router.put("/code", tokenAuth, teamController.teamToken);