From 506ff05e985d852a1eac7535b8f2d5da4925facf Mon Sep 17 00:00:00 2001 From: Andrej Date: Fri, 21 Feb 2025 08:29:14 +0100 Subject: [PATCH 1/4] feat: combining members and invites table into one --- ...n_guardian.sql => 0000_colorful_leech.sql} | 15 +- ...lin.sql => 0001_sparkling_jane_foster.sql} | 15 +- apps/api/drizzle/meta/0000_snapshot.json | 849 +++++++++--------- apps/api/drizzle/meta/0001_snapshot.json | 849 +++++++++--------- apps/api/drizzle/meta/_journal.json | 38 +- apps/api/src/database/index.ts | 2 - apps/api/src/database/schema.ts | 3 +- apps/api/src/index.ts | 6 + .../controllers/delete-workspace-user.ts | 19 + .../get-pending-workspace-users.ts | 29 - .../controllers/get-workspace-users.ts | 37 +- apps/api/src/workspace-user/index.ts | 39 +- .../workspace/controllers/create-workspace.ts | 8 +- .../team/delete-team-member-modal.tsx | 84 ++ .../team/invite-team-member-modal.tsx | 4 +- .../workspace-user/delete-workspace-user.ts | 18 + .../get-pending-workspace-users.ts | 17 - .../use-delete-workspace-user.ts | 15 + apps/web/src/routeTree.gen.ts | 29 - .../$workspaceId/_layout.invitations.tsx | 139 --- .../teams/$workspaceId/_layout.members.tsx | 185 ++-- .../dashboard/teams/$workspaceId/_layout.tsx | 38 +- 22 files changed, 1250 insertions(+), 1188 deletions(-) rename apps/api/drizzle/{0000_conscious_golden_guardian.sql => 0000_colorful_leech.sql} (81%) rename apps/api/drizzle/{0001_safe_green_goblin.sql => 0001_sparkling_jane_foster.sql} (85%) create mode 100644 apps/api/src/workspace-user/controllers/delete-workspace-user.ts delete mode 100644 apps/api/src/workspace-user/controllers/get-pending-workspace-users.ts create mode 100644 apps/web/src/components/team/delete-team-member-modal.tsx create mode 100644 apps/web/src/fetchers/workspace-user/delete-workspace-user.ts delete mode 100644 apps/web/src/fetchers/workspace-user/get-pending-workspace-users.ts create mode 100644 apps/web/src/hooks/mutations/workspace-user/use-delete-workspace-user.ts delete mode 100644 apps/web/src/routes/dashboard/teams/$workspaceId/_layout.invitations.tsx diff --git a/apps/api/drizzle/0000_conscious_golden_guardian.sql b/apps/api/drizzle/0000_colorful_leech.sql similarity index 81% rename from apps/api/drizzle/0000_conscious_golden_guardian.sql rename to apps/api/drizzle/0000_colorful_leech.sql index b36ca9d..1d92cae 100644 --- a/apps/api/drizzle/0000_conscious_golden_guardian.sql +++ b/apps/api/drizzle/0000_colorful_leech.sql @@ -5,7 +5,7 @@ CREATE TABLE `project` ( `icon` text DEFAULT 'Layout', `name` text NOT NULL, `description` text, - `created_at` integer DEFAULT '"2025-02-09T21:03:20.815Z"' NOT NULL, + `created_at` integer DEFAULT '"2025-02-18T21:46:28.196Z"' NOT NULL, FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON UPDATE cascade ON DELETE cascade ); --> statement-breakpoint @@ -20,13 +20,13 @@ CREATE TABLE `task` ( `id` text PRIMARY KEY NOT NULL, `project_id` text NOT NULL, `number` integer DEFAULT 1, - `assignee_email` text NOT NULL, + `assignee_email` text, `title` text NOT NULL, `description` text, `status` text DEFAULT 'to-do' NOT NULL, `priority` text DEFAULT 'low', `due_date` integer, - `created_at` integer DEFAULT '"2025-02-09T21:03:20.815Z"' NOT NULL, + `created_at` integer DEFAULT '"2025-02-18T21:46:28.196Z"' NOT NULL, FOREIGN KEY (`project_id`) REFERENCES `project`(`id`) ON UPDATE cascade ON DELETE cascade, FOREIGN KEY (`assignee_email`) REFERENCES `user`(`email`) ON UPDATE cascade ON DELETE cascade ); @@ -36,7 +36,7 @@ CREATE TABLE `user` ( `name` text NOT NULL, `password` text NOT NULL, `email` text NOT NULL, - `created_at` integer DEFAULT '"2025-02-09T21:03:20.814Z"' NOT NULL + `created_at` integer DEFAULT '"2025-02-18T21:46:28.196Z"' NOT NULL ); --> statement-breakpoint CREATE UNIQUE INDEX `user_email_unique` ON `user` (`email`);--> statement-breakpoint @@ -45,7 +45,7 @@ CREATE TABLE `workspace` ( `name` text NOT NULL, `description` text, `owner_email` text NOT NULL, - `created_at` integer DEFAULT '"2025-02-09T21:03:20.815Z"' NOT NULL, + `created_at` integer DEFAULT '"2025-02-18T21:46:28.196Z"' NOT NULL, FOREIGN KEY (`owner_email`) REFERENCES `user`(`email`) ON UPDATE cascade ON DELETE cascade ); --> statement-breakpoint @@ -53,8 +53,9 @@ CREATE TABLE `workspace_member` ( `id` text PRIMARY KEY NOT NULL, `workspace_id` text NOT NULL, `user_email` text NOT NULL, - `role` text, - `joined_at` integer DEFAULT '"2025-02-09T21:03:20.815Z"' NOT NULL, + `role` text DEFAULT 'member' NOT NULL, + `joined_at` integer DEFAULT '"2025-02-18T21:46:28.196Z"' NOT NULL, + `status` text DEFAULT 'active' NOT NULL, FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON UPDATE cascade ON DELETE cascade, FOREIGN KEY (`user_email`) REFERENCES `user`(`email`) ON UPDATE cascade ON DELETE cascade ); diff --git a/apps/api/drizzle/0001_safe_green_goblin.sql b/apps/api/drizzle/0001_sparkling_jane_foster.sql similarity index 85% rename from apps/api/drizzle/0001_safe_green_goblin.sql rename to apps/api/drizzle/0001_sparkling_jane_foster.sql index 36cdaf9..8688f57 100644 --- a/apps/api/drizzle/0001_safe_green_goblin.sql +++ b/apps/api/drizzle/0001_sparkling_jane_foster.sql @@ -6,7 +6,7 @@ CREATE TABLE `__new_project` ( `icon` text DEFAULT 'Layout', `name` text NOT NULL, `description` text, - `created_at` integer DEFAULT '"2025-02-11T18:12:00.369Z"' NOT NULL, + `created_at` integer DEFAULT '"2025-02-18T21:46:39.855Z"' NOT NULL, FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON UPDATE cascade ON DELETE cascade ); --> statement-breakpoint @@ -24,7 +24,7 @@ CREATE TABLE `__new_task` ( `status` text DEFAULT 'to-do' NOT NULL, `priority` text DEFAULT 'low', `due_date` integer, - `created_at` integer DEFAULT '"2025-02-11T18:12:00.369Z"' NOT NULL, + `created_at` integer DEFAULT '"2025-02-18T21:46:39.856Z"' NOT NULL, FOREIGN KEY (`project_id`) REFERENCES `project`(`id`) ON UPDATE cascade ON DELETE cascade, FOREIGN KEY (`assignee_email`) REFERENCES `user`(`email`) ON UPDATE cascade ON DELETE cascade ); @@ -37,7 +37,7 @@ CREATE TABLE `__new_user` ( `name` text NOT NULL, `password` text NOT NULL, `email` text NOT NULL, - `created_at` integer DEFAULT '"2025-02-11T18:12:00.368Z"' NOT NULL + `created_at` integer DEFAULT '"2025-02-18T21:46:39.855Z"' NOT NULL ); --> statement-breakpoint INSERT INTO `__new_user`("id", "name", "password", "email", "created_at") SELECT "id", "name", "password", "email", "created_at" FROM `user`;--> statement-breakpoint @@ -49,7 +49,7 @@ CREATE TABLE `__new_workspace` ( `name` text NOT NULL, `description` text, `owner_email` text NOT NULL, - `created_at` integer DEFAULT '"2025-02-11T18:12:00.368Z"' NOT NULL, + `created_at` integer DEFAULT '"2025-02-18T21:46:39.855Z"' NOT NULL, FOREIGN KEY (`owner_email`) REFERENCES `user`(`email`) ON UPDATE cascade ON DELETE cascade ); --> statement-breakpoint @@ -60,12 +60,13 @@ CREATE TABLE `__new_workspace_member` ( `id` text PRIMARY KEY NOT NULL, `workspace_id` text NOT NULL, `user_email` text NOT NULL, - `role` text, - `joined_at` integer DEFAULT '"2025-02-11T18:12:00.369Z"' NOT NULL, + `role` text DEFAULT 'member' NOT NULL, + `joined_at` integer DEFAULT '"2025-02-18T21:46:39.855Z"' NOT NULL, + `status` text DEFAULT 'active' NOT NULL, FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON UPDATE cascade ON DELETE cascade, FOREIGN KEY (`user_email`) REFERENCES `user`(`email`) ON UPDATE cascade ON DELETE cascade ); --> statement-breakpoint -INSERT INTO `__new_workspace_member`("id", "workspace_id", "user_email", "role", "joined_at") SELECT "id", "workspace_id", "user_email", "role", "joined_at" FROM `workspace_member`;--> statement-breakpoint +INSERT INTO `__new_workspace_member`("id", "workspace_id", "user_email", "role", "joined_at", "status") SELECT "id", "workspace_id", "user_email", "role", "joined_at", "status" FROM `workspace_member`;--> statement-breakpoint DROP TABLE `workspace_member`;--> statement-breakpoint ALTER TABLE `__new_workspace_member` RENAME TO `workspace_member`; \ No newline at end of file diff --git a/apps/api/drizzle/meta/0000_snapshot.json b/apps/api/drizzle/meta/0000_snapshot.json index 4959556..868f9af 100644 --- a/apps/api/drizzle/meta/0000_snapshot.json +++ b/apps/api/drizzle/meta/0000_snapshot.json @@ -1,406 +1,445 @@ { - "version": "6", - "dialect": "sqlite", - "id": "49787b32-4e2b-4274-a0e2-d2784a178329", - "prevId": "00000000-0000-0000-0000-000000000000", - "tables": { - "project": { - "name": "project", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "icon": { - "name": "icon", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'Layout'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'\"2025-02-09T21:03:20.815Z\"'" - } - }, - "indexes": {}, - "foreignKeys": { - "project_workspace_id_workspace_id_fk": { - "name": "project_workspace_id_workspace_id_fk", - "tableFrom": "project", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "session": { - "name": "session", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "session_user_id_user_id_fk": { - "name": "session_user_id_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "task": { - "name": "task", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "number": { - "name": "number", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 1 - }, - "assignee_email": { - "name": "assignee_email", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'to-do'" - }, - "priority": { - "name": "priority", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'low'" - }, - "due_date": { - "name": "due_date", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'\"2025-02-09T21:03:20.815Z\"'" - } - }, - "indexes": {}, - "foreignKeys": { - "task_project_id_project_id_fk": { - "name": "task_project_id_project_id_fk", - "tableFrom": "task", - "tableTo": "project", - "columnsFrom": ["project_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "cascade" - }, - "task_assignee_email_user_email_fk": { - "name": "task_assignee_email_user_email_fk", - "tableFrom": "task", - "tableTo": "user", - "columnsFrom": ["assignee_email"], - "columnsTo": ["email"], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "user": { - "name": "user", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "password": { - "name": "password", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'\"2025-02-09T21:03:20.814Z\"'" - } - }, - "indexes": { - "user_email_unique": { - "name": "user_email_unique", - "columns": ["email"], - "isUnique": true - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "workspace": { - "name": "workspace", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "owner_email": { - "name": "owner_email", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'\"2025-02-09T21:03:20.815Z\"'" - } - }, - "indexes": {}, - "foreignKeys": { - "workspace_owner_email_user_email_fk": { - "name": "workspace_owner_email_user_email_fk", - "tableFrom": "workspace", - "tableTo": "user", - "columnsFrom": ["owner_email"], - "columnsTo": ["email"], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "workspace_member": { - "name": "workspace_member", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "user_email": { - "name": "user_email", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "role": { - "name": "role", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "joined_at": { - "name": "joined_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'\"2025-02-09T21:03:20.815Z\"'" - } - }, - "indexes": {}, - "foreignKeys": { - "workspace_member_workspace_id_workspace_id_fk": { - "name": "workspace_member_workspace_id_workspace_id_fk", - "tableFrom": "workspace_member", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "cascade" - }, - "workspace_member_user_email_user_email_fk": { - "name": "workspace_member_user_email_user_email_fk", - "tableFrom": "workspace_member", - "tableTo": "user", - "columnsFrom": ["user_email"], - "columnsTo": ["email"], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - } - }, - "views": {}, - "enums": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "indexes": {} - } -} + "version": "6", + "dialect": "sqlite", + "id": "b293274e-7c85-4fc2-b89c-16dc01c10984", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "project": { + "name": "project", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'Layout'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'\"2025-02-18T21:46:28.196Z\"'" + } + }, + "indexes": {}, + "foreignKeys": { + "project_workspace_id_workspace_id_fk": { + "name": "project_workspace_id_workspace_id_fk", + "tableFrom": "project", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "task": { + "name": "task", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "number": { + "name": "number", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 1 + }, + "assignee_email": { + "name": "assignee_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'to-do'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'low'" + }, + "due_date": { + "name": "due_date", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'\"2025-02-18T21:46:28.196Z\"'" + } + }, + "indexes": {}, + "foreignKeys": { + "task_project_id_project_id_fk": { + "name": "task_project_id_project_id_fk", + "tableFrom": "task", + "tableTo": "project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "task_assignee_email_user_email_fk": { + "name": "task_assignee_email_user_email_fk", + "tableFrom": "task", + "tableTo": "user", + "columnsFrom": [ + "assignee_email" + ], + "columnsTo": [ + "email" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'\"2025-02-18T21:46:28.196Z\"'" + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workspace": { + "name": "workspace", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "owner_email": { + "name": "owner_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'\"2025-02-18T21:46:28.196Z\"'" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_owner_email_user_email_fk": { + "name": "workspace_owner_email_user_email_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": [ + "owner_email" + ], + "columnsTo": [ + "email" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workspace_member": { + "name": "workspace_member", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_email": { + "name": "user_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'member'" + }, + "joined_at": { + "name": "joined_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'\"2025-02-18T21:46:28.196Z\"'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_member_workspace_id_workspace_id_fk": { + "name": "workspace_member_workspace_id_workspace_id_fk", + "tableFrom": "workspace_member", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "workspace_member_user_email_user_email_fk": { + "name": "workspace_member_user_email_user_email_fk", + "tableFrom": "workspace_member", + "tableTo": "user", + "columnsFrom": [ + "user_email" + ], + "columnsTo": [ + "email" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/apps/api/drizzle/meta/0001_snapshot.json b/apps/api/drizzle/meta/0001_snapshot.json index 93f020d..d07fb9b 100644 --- a/apps/api/drizzle/meta/0001_snapshot.json +++ b/apps/api/drizzle/meta/0001_snapshot.json @@ -1,406 +1,445 @@ { - "version": "6", - "dialect": "sqlite", - "id": "e2e57331-358c-4168-862a-fcbeb44be8b0", - "prevId": "49787b32-4e2b-4274-a0e2-d2784a178329", - "tables": { - "project": { - "name": "project", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "icon": { - "name": "icon", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'Layout'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'\"2025-02-11T18:12:00.369Z\"'" - } - }, - "indexes": {}, - "foreignKeys": { - "project_workspace_id_workspace_id_fk": { - "name": "project_workspace_id_workspace_id_fk", - "tableFrom": "project", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "session": { - "name": "session", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "session_user_id_user_id_fk": { - "name": "session_user_id_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "task": { - "name": "task", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "number": { - "name": "number", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 1 - }, - "assignee_email": { - "name": "assignee_email", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'to-do'" - }, - "priority": { - "name": "priority", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'low'" - }, - "due_date": { - "name": "due_date", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'\"2025-02-11T18:12:00.369Z\"'" - } - }, - "indexes": {}, - "foreignKeys": { - "task_project_id_project_id_fk": { - "name": "task_project_id_project_id_fk", - "tableFrom": "task", - "tableTo": "project", - "columnsFrom": ["project_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "cascade" - }, - "task_assignee_email_user_email_fk": { - "name": "task_assignee_email_user_email_fk", - "tableFrom": "task", - "tableTo": "user", - "columnsFrom": ["assignee_email"], - "columnsTo": ["email"], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "user": { - "name": "user", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "password": { - "name": "password", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'\"2025-02-11T18:12:00.368Z\"'" - } - }, - "indexes": { - "user_email_unique": { - "name": "user_email_unique", - "columns": ["email"], - "isUnique": true - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "workspace": { - "name": "workspace", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "owner_email": { - "name": "owner_email", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'\"2025-02-11T18:12:00.368Z\"'" - } - }, - "indexes": {}, - "foreignKeys": { - "workspace_owner_email_user_email_fk": { - "name": "workspace_owner_email_user_email_fk", - "tableFrom": "workspace", - "tableTo": "user", - "columnsFrom": ["owner_email"], - "columnsTo": ["email"], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "workspace_member": { - "name": "workspace_member", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "user_email": { - "name": "user_email", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "role": { - "name": "role", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "joined_at": { - "name": "joined_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'\"2025-02-11T18:12:00.369Z\"'" - } - }, - "indexes": {}, - "foreignKeys": { - "workspace_member_workspace_id_workspace_id_fk": { - "name": "workspace_member_workspace_id_workspace_id_fk", - "tableFrom": "workspace_member", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "cascade" - }, - "workspace_member_user_email_user_email_fk": { - "name": "workspace_member_user_email_user_email_fk", - "tableFrom": "workspace_member", - "tableTo": "user", - "columnsFrom": ["user_email"], - "columnsTo": ["email"], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - } - }, - "views": {}, - "enums": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "indexes": {} - } -} + "version": "6", + "dialect": "sqlite", + "id": "6058e5ae-5394-46de-b3a1-edd4f121bb0d", + "prevId": "b293274e-7c85-4fc2-b89c-16dc01c10984", + "tables": { + "project": { + "name": "project", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'Layout'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'\"2025-02-18T21:46:39.855Z\"'" + } + }, + "indexes": {}, + "foreignKeys": { + "project_workspace_id_workspace_id_fk": { + "name": "project_workspace_id_workspace_id_fk", + "tableFrom": "project", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "task": { + "name": "task", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "number": { + "name": "number", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 1 + }, + "assignee_email": { + "name": "assignee_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'to-do'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'low'" + }, + "due_date": { + "name": "due_date", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'\"2025-02-18T21:46:39.856Z\"'" + } + }, + "indexes": {}, + "foreignKeys": { + "task_project_id_project_id_fk": { + "name": "task_project_id_project_id_fk", + "tableFrom": "task", + "tableTo": "project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "task_assignee_email_user_email_fk": { + "name": "task_assignee_email_user_email_fk", + "tableFrom": "task", + "tableTo": "user", + "columnsFrom": [ + "assignee_email" + ], + "columnsTo": [ + "email" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'\"2025-02-18T21:46:39.855Z\"'" + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workspace": { + "name": "workspace", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "owner_email": { + "name": "owner_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'\"2025-02-18T21:46:39.855Z\"'" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_owner_email_user_email_fk": { + "name": "workspace_owner_email_user_email_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": [ + "owner_email" + ], + "columnsTo": [ + "email" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workspace_member": { + "name": "workspace_member", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_email": { + "name": "user_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'member'" + }, + "joined_at": { + "name": "joined_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'\"2025-02-18T21:46:39.855Z\"'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_member_workspace_id_workspace_id_fk": { + "name": "workspace_member_workspace_id_workspace_id_fk", + "tableFrom": "workspace_member", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "workspace_member_user_email_user_email_fk": { + "name": "workspace_member_user_email_user_email_fk", + "tableFrom": "workspace_member", + "tableTo": "user", + "columnsFrom": [ + "user_email" + ], + "columnsTo": [ + "email" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/apps/api/drizzle/meta/_journal.json b/apps/api/drizzle/meta/_journal.json index 2d73386..7695752 100644 --- a/apps/api/drizzle/meta/_journal.json +++ b/apps/api/drizzle/meta/_journal.json @@ -1,20 +1,20 @@ { - "version": "7", - "dialect": "sqlite", - "entries": [ - { - "idx": 0, - "version": "6", - "when": 1739135000822, - "tag": "0000_conscious_golden_guardian", - "breakpoints": true - }, - { - "idx": 1, - "version": "6", - "when": 1739297520379, - "tag": "0001_safe_green_goblin", - "breakpoints": true - } - ] -} + "version": "7", + "dialect": "sqlite", + "entries": [ + { + "idx": 0, + "version": "6", + "when": 1739915188203, + "tag": "0000_colorful_leech", + "breakpoints": true + }, + { + "idx": 1, + "version": "6", + "when": 1739915199865, + "tag": "0001_sparkling_jane_foster", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/apps/api/src/database/index.ts b/apps/api/src/database/index.ts index 2c1f364..cd66b88 100644 --- a/apps/api/src/database/index.ts +++ b/apps/api/src/database/index.ts @@ -11,6 +11,4 @@ const sqlite = new Database(dbPath); const db = drizzle(sqlite, { schema }); -void migrate(db, { migrationsFolder: "drizzle" }); - export default db; diff --git a/apps/api/src/database/schema.ts b/apps/api/src/database/schema.ts index 5b4f130..7884fad 100644 --- a/apps/api/src/database/schema.ts +++ b/apps/api/src/database/schema.ts @@ -59,10 +59,11 @@ export const workspaceUserTable = sqliteTable("workspace_member", { onDelete: "cascade", onUpdate: "cascade", }), - role: text("role"), + role: text("role").default("member").notNull(), joinedAt: integer("joined_at", { mode: "timestamp" }) .default(new Date()) .notNull(), + status: text("status").default("pending").notNull(), }); export const projectTable = sqliteTable("project", { diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 207d6a3..2408a69 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -1,5 +1,7 @@ import { cors } from "@elysiajs/cors"; +import { migrate } from "drizzle-orm/better-sqlite3/migrator"; import { Elysia } from "elysia"; +import db from "./database"; import project from "./project"; import task from "./task"; import user from "./user"; @@ -58,4 +60,8 @@ const app = new Elysia() export type App = typeof app; +console.log("Migrating database..."); +migrate(db, { migrationsFolder: "drizzle" }); +console.log("Database migrated"); + console.log(`🏃 Kaneo is running at ${app.server?.url}`); diff --git a/apps/api/src/workspace-user/controllers/delete-workspace-user.ts b/apps/api/src/workspace-user/controllers/delete-workspace-user.ts new file mode 100644 index 0000000..167a51c --- /dev/null +++ b/apps/api/src/workspace-user/controllers/delete-workspace-user.ts @@ -0,0 +1,19 @@ +import { and, eq } from "drizzle-orm"; +import db from "../../database"; +import { workspaceUserTable } from "../../database/schema"; + +async function deleteWorkspaceUser({ + workspaceId, + userEmail, +}: { workspaceId: string; userEmail: string }) { + await db + .delete(workspaceUserTable) + .where( + and( + eq(workspaceUserTable.workspaceId, workspaceId), + eq(workspaceUserTable.userEmail, userEmail), + ), + ); +} + +export default deleteWorkspaceUser; diff --git a/apps/api/src/workspace-user/controllers/get-pending-workspace-users.ts b/apps/api/src/workspace-user/controllers/get-pending-workspace-users.ts deleted file mode 100644 index 7ec4e04..0000000 --- a/apps/api/src/workspace-user/controllers/get-pending-workspace-users.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { and, eq, isNull } from "drizzle-orm"; -import db from "../../database"; -import { userTable, workspaceUserTable } from "../../database/schema"; - -async function getPendingWorkspaceUsers({ - workspaceId, -}: { - workspaceId: string; -}) { - const pendingInvites = await db - .select({ - id: workspaceUserTable.id, - email: workspaceUserTable.userEmail, - role: workspaceUserTable.role, - invitedAt: workspaceUserTable.joinedAt, - }) - .from(workspaceUserTable) - .leftJoin(userTable, eq(workspaceUserTable.userEmail, userTable.email)) - .where( - and( - eq(workspaceUserTable.workspaceId, workspaceId), - isNull(userTable.email), - ), - ); - - return pendingInvites; -} - -export default getPendingWorkspaceUsers; diff --git a/apps/api/src/workspace-user/controllers/get-workspace-users.ts b/apps/api/src/workspace-user/controllers/get-workspace-users.ts index fb3506b..fbd4802 100644 --- a/apps/api/src/workspace-user/controllers/get-workspace-users.ts +++ b/apps/api/src/workspace-user/controllers/get-workspace-users.ts @@ -1,4 +1,4 @@ -import { eq } from "drizzle-orm"; +import { and, asc, desc, eq, not } from "drizzle-orm"; import db from "../../database"; import { userTable, @@ -9,28 +9,45 @@ import { function getWorkspaceUsers({ workspaceId }: { workspaceId: string }) { return db .select({ - userEmail: userTable.email, + userEmail: workspaceUserTable.userEmail, userName: userTable.name, - joinedAt: userTable.createdAt, + joinedAt: workspaceUserTable.joinedAt, + status: workspaceUserTable.status, + role: workspaceUserTable.role, }) .from(workspaceTable) - .where(eq(workspaceTable.id, workspaceId)) .innerJoin( workspaceUserTable, eq(workspaceTable.id, workspaceUserTable.workspaceId), ) - .innerJoin(userTable, eq(workspaceUserTable.userEmail, userTable.email)) + .leftJoin(userTable, eq(workspaceUserTable.userEmail, userTable.email)) + .where( + and( + eq(workspaceTable.id, workspaceId), + not(eq(workspaceUserTable.userEmail, workspaceTable.ownerEmail)), + ), + ) .unionAll( db .select({ - userEmail: userTable.email, + userEmail: workspaceUserTable.userEmail, userName: userTable.name, - joinedAt: userTable.createdAt, + joinedAt: workspaceUserTable.joinedAt, + status: workspaceUserTable.status, + role: workspaceUserTable.role, }) .from(workspaceTable) - .innerJoin(userTable, eq(workspaceTable.ownerEmail, userTable.email)) - .where(eq(workspaceTable.id, workspaceId)), - ); + .where(eq(workspaceTable.id, workspaceId)) + .innerJoin( + workspaceUserTable, + and( + eq(workspaceTable.id, workspaceUserTable.workspaceId), + eq(workspaceUserTable.userEmail, workspaceTable.ownerEmail), + ), + ) + .leftJoin(userTable, eq(workspaceUserTable.userEmail, userTable.email)), + ) + .orderBy(asc(workspaceUserTable.joinedAt)); } export default getWorkspaceUsers; diff --git a/apps/api/src/workspace-user/index.ts b/apps/api/src/workspace-user/index.ts index 3fe7623..f6d8abd 100644 --- a/apps/api/src/workspace-user/index.ts +++ b/apps/api/src/workspace-user/index.ts @@ -1,21 +1,24 @@ import Elysia, { t } from "elysia"; -import getPendingWorkspaceUsers from "./controllers/get-pending-workspace-users"; +import deleteWorkspaceUser from "./controllers/delete-workspace-user"; import getWorkspaceUsers from "./controllers/get-workspace-users"; import inviteWorkspaceUser from "./controllers/invite-workspace-user"; const workspaceUser = new Elysia({ prefix: "/workspace-user" }) - .get("/list/:workspaceId", async ({ params: { workspaceId } }) => { - const workspaceUsersInWorkspace = await getWorkspaceUsers({ workspaceId }); + .get( + "/list/:workspaceId", + async ({ params: { workspaceId } }) => { + const workspaceUsersInWorkspace = await getWorkspaceUsers({ + workspaceId, + }); - return workspaceUsersInWorkspace; - }) - .get("/pending/list/:workspaceId", async ({ params: { workspaceId } }) => { - const pendingWorkspaceUsersInWorkspace = await getPendingWorkspaceUsers({ - workspaceId, - }); - - return pendingWorkspaceUsersInWorkspace; - }) + return workspaceUsersInWorkspace; + }, + { + params: t.Object({ + workspaceId: t.String(), + }), + }, + ) .post( "/:workspaceId/invite", async ({ body }) => { @@ -29,6 +32,18 @@ const workspaceUser = new Elysia({ prefix: "/workspace-user" }) workspaceId: t.String(), }), }, + ) + .delete( + "/:workspaceId/:userEmail", + async ({ params: { workspaceId, userEmail } }) => { + await deleteWorkspaceUser({ workspaceId, userEmail }); + }, + { + params: t.Object({ + workspaceId: t.String(), + userEmail: t.String(), + }), + }, ); export default workspaceUser; diff --git a/apps/api/src/workspace/controllers/create-workspace.ts b/apps/api/src/workspace/controllers/create-workspace.ts index 3088d14..af8a745 100644 --- a/apps/api/src/workspace/controllers/create-workspace.ts +++ b/apps/api/src/workspace/controllers/create-workspace.ts @@ -1,9 +1,15 @@ import db from "../../database"; -import { workspaceTable } from "../../database/schema"; +import { workspaceTable, workspaceUserTable } from "../../database/schema"; import type { CreateWorkspacePayload } from "../db/queries"; async function createWorkspace(body: CreateWorkspacePayload) { const [workspace] = await db.insert(workspaceTable).values(body).returning(); + await db.insert(workspaceUserTable).values({ + workspaceId: workspace.id, + userEmail: body.ownerEmail, + role: "owner", + status: "active", + }); return workspace; } diff --git a/apps/web/src/components/team/delete-team-member-modal.tsx b/apps/web/src/components/team/delete-team-member-modal.tsx new file mode 100644 index 0000000..dadcb73 --- /dev/null +++ b/apps/web/src/components/team/delete-team-member-modal.tsx @@ -0,0 +1,84 @@ +import useDeleteWorkspaceUser from "@/hooks/mutations/workspace-user/use-delete-workspace-user"; +import { Route } from "@/routes/dashboard/teams/$workspaceId/_layout"; +import * as Dialog from "@radix-ui/react-dialog"; +import { useQueryClient } from "@tanstack/react-query"; +import { Trash2, X } from "lucide-react"; +import { Button } from "../ui/button"; +function DeleteTeamMemberModal({ + userEmail, + open, + onClose, +}: { + userEmail: string; + open: boolean; + onClose: () => void; +}) { + const { workspaceId } = Route.useParams(); + const { mutateAsync: deleteWorkspaceUser } = useDeleteWorkspaceUser(); + const queryClient = useQueryClient(); + + console.log({ workspaceId, userEmail }); + + const onRemoveMember = async () => { + await deleteWorkspaceUser({ + workspaceId: workspaceId, + userEmail: userEmail, + }); + + queryClient.invalidateQueries({ + queryKey: ["workspace-users"], + }); + + onClose(); + }; + + return ( + + + + +
+
+ + Remove Team Member + + + + +
+ +
+

+ Are you sure you want to remove{" "} + + {userEmail} + {" "} + from the team? This action cannot be undone. +

+ +
+ + + + +
+
+
+
+
+
+ ); +} + +export default DeleteTeamMemberModal; diff --git a/apps/web/src/components/team/invite-team-member-modal.tsx b/apps/web/src/components/team/invite-team-member-modal.tsx index b4d56a1..1bb4dd2 100644 --- a/apps/web/src/components/team/invite-team-member-modal.tsx +++ b/apps/web/src/components/team/invite-team-member-modal.tsx @@ -1,5 +1,5 @@ import useInviteWorkspaceUser from "@/hooks/mutations/workspace-user/use-invite-workspace-user"; -import { Route } from "@/routes/dashboard/teams/$workspaceId/_layout.invitations"; +import { Route } from "@/routes/dashboard/teams/$workspaceId/_layout"; import { zodResolver } from "@hookform/resolvers/zod"; import * as Dialog from "@radix-ui/react-dialog"; import { useQueryClient } from "@tanstack/react-query"; @@ -43,7 +43,7 @@ function InviteTeamMemberModal({ open, onClose }: Props) { const onSubmit = async ({ userEmail }: TeamMemberFormValues) => { await mutateAsync({ userEmail, workspaceId }); await queryClient.refetchQueries({ - queryKey: ["pending-workspace-users", workspaceId], + queryKey: ["workspace-users", workspaceId], }); form.reset(); diff --git a/apps/web/src/fetchers/workspace-user/delete-workspace-user.ts b/apps/web/src/fetchers/workspace-user/delete-workspace-user.ts new file mode 100644 index 0000000..017a5e3 --- /dev/null +++ b/apps/web/src/fetchers/workspace-user/delete-workspace-user.ts @@ -0,0 +1,18 @@ +import { api } from "@kaneo/libs/src/eden"; + +async function deleteWorkspaceUser({ + workspaceId, + userEmail, +}: { workspaceId: string; userEmail: string }) { + const response = await api["workspace-user"]({ workspaceId })({ + userEmail, + }).delete(); + + if (response.error) { + throw new Error(response.error.value.message); + } + + return response.data; +} + +export default deleteWorkspaceUser; diff --git a/apps/web/src/fetchers/workspace-user/get-pending-workspace-users.ts b/apps/web/src/fetchers/workspace-user/get-pending-workspace-users.ts deleted file mode 100644 index 7ff10df..0000000 --- a/apps/web/src/fetchers/workspace-user/get-pending-workspace-users.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { api } from "@kaneo/libs"; - -async function getPendingWorkspaceUsers({ - workspaceId, -}: { workspaceId: string }) { - const response = await api["workspace-user"].pending - .list({ workspaceId }) - .get(); - - if (response.error) { - throw new Error(response.error.value.message); - } - - return response.data; -} - -export default getPendingWorkspaceUsers; diff --git a/apps/web/src/hooks/mutations/workspace-user/use-delete-workspace-user.ts b/apps/web/src/hooks/mutations/workspace-user/use-delete-workspace-user.ts new file mode 100644 index 0000000..6726cda --- /dev/null +++ b/apps/web/src/hooks/mutations/workspace-user/use-delete-workspace-user.ts @@ -0,0 +1,15 @@ +import { useMutation } from "@tanstack/react-query"; + +import deleteWorkspaceUser from "@/fetchers/workspace-user/delete-workspace-user"; + +function useDeleteWorkspaceUser() { + return useMutation({ + mutationFn: ({ + workspaceId, + userEmail, + }: { workspaceId: string; userEmail: string }) => + deleteWorkspaceUser({ workspaceId, userEmail }), + }); +} + +export default useDeleteWorkspaceUser; diff --git a/apps/web/src/routeTree.gen.ts b/apps/web/src/routeTree.gen.ts index b262f00..0b40088 100644 --- a/apps/web/src/routeTree.gen.ts +++ b/apps/web/src/routeTree.gen.ts @@ -24,7 +24,6 @@ import { Route as DashboardTeamsWorkspaceIdLayoutImport } from './routes/dashboa import { Route as DashboardWorkspaceWorkspaceIdProjectProjectIdImport } from './routes/dashboard/workspace/$workspaceId/project/$projectId' import { Route as DashboardTeamsWorkspaceIdLayoutRolesImport } from './routes/dashboard/teams/$workspaceId/_layout.roles' import { Route as DashboardTeamsWorkspaceIdLayoutMembersImport } from './routes/dashboard/teams/$workspaceId/_layout.members' -import { Route as DashboardTeamsWorkspaceIdLayoutInvitationsImport } from './routes/dashboard/teams/$workspaceId/_layout.invitations' import { Route as DashboardWorkspaceWorkspaceIdProjectProjectIdBoardImport } from './routes/dashboard/workspace/$workspaceId/project/$projectId/board' import { Route as DashboardWorkspaceWorkspaceIdProjectProjectIdTaskTaskIdImport } from './routes/dashboard/workspace/$workspaceId/project/$projectId/task/$taskId' @@ -113,13 +112,6 @@ const DashboardTeamsWorkspaceIdLayoutMembersRoute = getParentRoute: () => DashboardTeamsWorkspaceIdLayoutRoute, } as any) -const DashboardTeamsWorkspaceIdLayoutInvitationsRoute = - DashboardTeamsWorkspaceIdLayoutInvitationsImport.update({ - id: '/invitations', - path: '/invitations', - getParentRoute: () => DashboardTeamsWorkspaceIdLayoutRoute, - } as any) - const DashboardWorkspaceWorkspaceIdProjectProjectIdBoardRoute = DashboardWorkspaceWorkspaceIdProjectProjectIdBoardImport.update({ id: '/board', @@ -201,13 +193,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DashboardTeamsWorkspaceIdLayoutImport parentRoute: typeof DashboardTeamsWorkspaceIdRoute } - '/dashboard/teams/$workspaceId/_layout/invitations': { - id: '/dashboard/teams/$workspaceId/_layout/invitations' - path: '/invitations' - fullPath: '/dashboard/teams/$workspaceId/invitations' - preLoaderRoute: typeof DashboardTeamsWorkspaceIdLayoutInvitationsImport - parentRoute: typeof DashboardTeamsWorkspaceIdLayoutImport - } '/dashboard/teams/$workspaceId/_layout/members': { id: '/dashboard/teams/$workspaceId/_layout/members' path: '/members' @@ -293,15 +278,12 @@ const DashboardWorkspaceWorkspaceIdRouteWithChildren = ) interface DashboardTeamsWorkspaceIdLayoutRouteChildren { - DashboardTeamsWorkspaceIdLayoutInvitationsRoute: typeof DashboardTeamsWorkspaceIdLayoutInvitationsRoute DashboardTeamsWorkspaceIdLayoutMembersRoute: typeof DashboardTeamsWorkspaceIdLayoutMembersRoute DashboardTeamsWorkspaceIdLayoutRolesRoute: typeof DashboardTeamsWorkspaceIdLayoutRolesRoute } const DashboardTeamsWorkspaceIdLayoutRouteChildren: DashboardTeamsWorkspaceIdLayoutRouteChildren = { - DashboardTeamsWorkspaceIdLayoutInvitationsRoute: - DashboardTeamsWorkspaceIdLayoutInvitationsRoute, DashboardTeamsWorkspaceIdLayoutMembersRoute: DashboardTeamsWorkspaceIdLayoutMembersRoute, DashboardTeamsWorkspaceIdLayoutRolesRoute: @@ -354,7 +336,6 @@ export interface FileRoutesByFullPath { '/dashboard/settings/appearance': typeof DashboardSettingsAppearanceRoute '/dashboard/workspace/$workspaceId': typeof DashboardWorkspaceWorkspaceIdRouteWithChildren '/dashboard/teams/$workspaceId': typeof DashboardTeamsWorkspaceIdLayoutRouteWithChildren - '/dashboard/teams/$workspaceId/invitations': typeof DashboardTeamsWorkspaceIdLayoutInvitationsRoute '/dashboard/teams/$workspaceId/members': typeof DashboardTeamsWorkspaceIdLayoutMembersRoute '/dashboard/teams/$workspaceId/roles': typeof DashboardTeamsWorkspaceIdLayoutRolesRoute '/dashboard/workspace/$workspaceId/project/$projectId': typeof DashboardWorkspaceWorkspaceIdProjectProjectIdRouteWithChildren @@ -371,7 +352,6 @@ export interface FileRoutesByTo { '/dashboard/settings/appearance': typeof DashboardSettingsAppearanceRoute '/dashboard/workspace/$workspaceId': typeof DashboardWorkspaceWorkspaceIdRouteWithChildren '/dashboard/teams/$workspaceId': typeof DashboardTeamsWorkspaceIdLayoutRouteWithChildren - '/dashboard/teams/$workspaceId/invitations': typeof DashboardTeamsWorkspaceIdLayoutInvitationsRoute '/dashboard/teams/$workspaceId/members': typeof DashboardTeamsWorkspaceIdLayoutMembersRoute '/dashboard/teams/$workspaceId/roles': typeof DashboardTeamsWorkspaceIdLayoutRolesRoute '/dashboard/workspace/$workspaceId/project/$projectId': typeof DashboardWorkspaceWorkspaceIdProjectProjectIdRouteWithChildren @@ -390,7 +370,6 @@ export interface FileRoutesById { '/dashboard/workspace/$workspaceId': typeof DashboardWorkspaceWorkspaceIdRouteWithChildren '/dashboard/teams/$workspaceId': typeof DashboardTeamsWorkspaceIdRouteWithChildren '/dashboard/teams/$workspaceId/_layout': typeof DashboardTeamsWorkspaceIdLayoutRouteWithChildren - '/dashboard/teams/$workspaceId/_layout/invitations': typeof DashboardTeamsWorkspaceIdLayoutInvitationsRoute '/dashboard/teams/$workspaceId/_layout/members': typeof DashboardTeamsWorkspaceIdLayoutMembersRoute '/dashboard/teams/$workspaceId/_layout/roles': typeof DashboardTeamsWorkspaceIdLayoutRolesRoute '/dashboard/workspace/$workspaceId/project/$projectId': typeof DashboardWorkspaceWorkspaceIdProjectProjectIdRouteWithChildren @@ -409,7 +388,6 @@ export interface FileRouteTypes { | '/dashboard/settings/appearance' | '/dashboard/workspace/$workspaceId' | '/dashboard/teams/$workspaceId' - | '/dashboard/teams/$workspaceId/invitations' | '/dashboard/teams/$workspaceId/members' | '/dashboard/teams/$workspaceId/roles' | '/dashboard/workspace/$workspaceId/project/$projectId' @@ -425,7 +403,6 @@ export interface FileRouteTypes { | '/dashboard/settings/appearance' | '/dashboard/workspace/$workspaceId' | '/dashboard/teams/$workspaceId' - | '/dashboard/teams/$workspaceId/invitations' | '/dashboard/teams/$workspaceId/members' | '/dashboard/teams/$workspaceId/roles' | '/dashboard/workspace/$workspaceId/project/$projectId' @@ -442,7 +419,6 @@ export interface FileRouteTypes { | '/dashboard/workspace/$workspaceId' | '/dashboard/teams/$workspaceId' | '/dashboard/teams/$workspaceId/_layout' - | '/dashboard/teams/$workspaceId/_layout/invitations' | '/dashboard/teams/$workspaceId/_layout/members' | '/dashboard/teams/$workspaceId/_layout/roles' | '/dashboard/workspace/$workspaceId/project/$projectId' @@ -527,15 +503,10 @@ export const routeTree = rootRoute "filePath": "dashboard/teams/$workspaceId/_layout.tsx", "parent": "/dashboard/teams/$workspaceId", "children": [ - "/dashboard/teams/$workspaceId/_layout/invitations", "/dashboard/teams/$workspaceId/_layout/members", "/dashboard/teams/$workspaceId/_layout/roles" ] }, - "/dashboard/teams/$workspaceId/_layout/invitations": { - "filePath": "dashboard/teams/$workspaceId/_layout.invitations.tsx", - "parent": "/dashboard/teams/$workspaceId/_layout" - }, "/dashboard/teams/$workspaceId/_layout/members": { "filePath": "dashboard/teams/$workspaceId/_layout.members.tsx", "parent": "/dashboard/teams/$workspaceId/_layout" diff --git a/apps/web/src/routes/dashboard/teams/$workspaceId/_layout.invitations.tsx b/apps/web/src/routes/dashboard/teams/$workspaceId/_layout.invitations.tsx deleted file mode 100644 index 4160254..0000000 --- a/apps/web/src/routes/dashboard/teams/$workspaceId/_layout.invitations.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import InviteTeamMemberModal from "@/components/team/invite-team-member-modal"; -import { Avatar, AvatarFallback } from "@/components/ui/avatar"; -import { Button } from "@/components/ui/button"; -import useGetPendingWorkspaceUsers from "@/hooks/queries/workspace-users/use-get-pending-workspace-users"; -import { createFileRoute } from "@tanstack/react-router"; -import { UserPlus } from "lucide-react"; -import { useState } from "react"; - -export const Route = createFileRoute( - "/dashboard/teams/$workspaceId/_layout/invitations", -)({ - component: RouteComponent, -}); - -function RouteComponent() { - const { workspaceId } = Route.useParams(); - const { data: users } = useGetPendingWorkspaceUsers({ workspaceId }); - const [isInviteTeamMemberModalOpen, setIsInviteTeamMemberModalOpen] = - useState(false); - - return ( -
-
-

- Team Invitations -

- -
- -
-
-
- {users?.map((invitation) => ( -
-
- - - {invitation.email[0]} - - -
-
- {invitation.email} -
-
- Invited{" "} - {new Date(invitation.invitedAt).toLocaleDateString( - "en-US", - { - year: "numeric", - month: "short", - day: "numeric", - }, - )} -
-
-
-
- ))} -
- - - - - - - - - - - {users?.map((invitation) => ( - - - - - - ))} - -
- Email - - Role - - Sent -
-
- - - {invitation.email[0]} - - - - {invitation.email} - -
-
- - Admin - - - {new Date(invitation.invitedAt).toLocaleDateString( - "en-US", - { - year: "numeric", - month: "short", - day: "numeric", - }, - )} -
-
- - {users?.length === 0 && ( -
-

- No invitations found -

-
- )} -
- - setIsInviteTeamMemberModalOpen(false)} - /> -
- ); -} diff --git a/apps/web/src/routes/dashboard/teams/$workspaceId/_layout.members.tsx b/apps/web/src/routes/dashboard/teams/$workspaceId/_layout.members.tsx index 90cc485..1d10ad3 100644 --- a/apps/web/src/routes/dashboard/teams/$workspaceId/_layout.members.tsx +++ b/apps/web/src/routes/dashboard/teams/$workspaceId/_layout.members.tsx @@ -1,8 +1,12 @@ +import DeleteTeamMemberModal from "@/components/team/delete-team-member-modal"; +import InviteTeamMemberModal from "@/components/team/invite-team-member-modal"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; +import { Button } from "@/components/ui/button"; import useGetWorkspaceUsers from "@/hooks/queries/workspace-users/use-get-workspace-users"; import { createFileRoute } from "@tanstack/react-router"; import { motion } from "framer-motion"; -import { Shield } from "lucide-react"; +import { CheckCircle2, Clock, MoreHorizontal, UserPlus } from "lucide-react"; +import { useState } from "react"; export const Route = createFileRoute( "/dashboard/teams/$workspaceId/_layout/members", @@ -10,9 +14,29 @@ export const Route = createFileRoute( component: RouteComponent, }); +const getStatusIcon = (status: "active" | "pending") => { + switch (status) { + case "active": + return ( + + ); + case "pending": + return ; + } +}; + +const getStatusText = (status: "active" | "pending") => + ({ + active: "Active", + pending: "Pending", + })[status]; + function RouteComponent() { const { workspaceId } = Route.useParams(); const { data: users } = useGetWorkspaceUsers({ workspaceId }); + const [isInviteTeamMemberModalOpen, setIsInviteTeamMemberModalOpen] = + useState(false); + const [isRemoveMemberModalOpen, setIsRemoveMemberModalOpen] = useState(false); return ( Team Members + +
-
-
+ + + + + + + + + + {users?.map((member) => ( - -
-
- - - {member.userName?.[0]} +
+ +
+ Email + + Role + + Status + + Date + +
+
+ + + {member.userEmail.charAt(0)} -
-
- {member.userName} -
-
- {member.userEmail} -
-
+ + {member.userEmail} +
- -
- - - Admin +
+ + {member.role.charAt(0).toUpperCase() + + member.role.slice(1).toLowerCase()} - - - ))} - - - - - - - - + + + + - - - {users?.map((member) => ( - - - - - - ))} - -
- Member - - Role - - Email - +
+ {getStatusIcon(member.status as "active" | "pending")} + + {getStatusText(member.status as "active" | "pending")} + +
+
+ {member.joinedAt && + new Date(member.joinedAt).toLocaleDateString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + })} + + +
-
- - - {member.userName?.[0]} - - - - {member.userName} - -
-
-
- - - Admin - -
-
- {member.userEmail} -
- + ))} +
{users?.length === 0 && (
@@ -123,6 +140,16 @@ function RouteComponent() {
)}
+ setIsRemoveMemberModalOpen(false)} + /> + + setIsInviteTeamMemberModalOpen(false)} + /> ); } diff --git a/apps/web/src/routes/dashboard/teams/$workspaceId/_layout.tsx b/apps/web/src/routes/dashboard/teams/$workspaceId/_layout.tsx index 2fbed8c..fd244ab 100644 --- a/apps/web/src/routes/dashboard/teams/$workspaceId/_layout.tsx +++ b/apps/web/src/routes/dashboard/teams/$workspaceId/_layout.tsx @@ -1,5 +1,7 @@ import { Button } from "@/components/ui/button"; +import useGetWorkspace from "@/hooks/queries/workspace/use-get-workspace"; import { cn } from "@/lib/cn"; +import useWorkspaceStore from "@/store/workspace"; import { Link, Outlet, @@ -9,7 +11,7 @@ import { import { AnimatePresence, motion } from "framer-motion"; import { Shield, UserPlus, Users } from "lucide-react"; import { Menu } from "lucide-react"; -import { useState } from "react"; +import { useEffect, useState } from "react"; export const Route = createFileRoute("/dashboard/teams/$workspaceId/_layout")({ component: RouteComponent, @@ -19,6 +21,17 @@ function RouteComponent() { const [isMobileNavOpen, setIsMobileNavOpen] = useState(false); const pathname = useRouterState({ select: (s) => s.location.pathname }); const { workspaceId } = Route.useParams(); + const { setWorkspace } = useWorkspaceStore(); + + const { data: workspace } = useGetWorkspace({ + workspaceId, + }); + + useEffect(() => { + if (workspace) { + setWorkspace(workspace); + } + }, [workspace, setWorkspace]); return (
@@ -98,29 +111,6 @@ function RouteComponent() { - - setIsMobileNavOpen(false)} - > - - Invitations - - - Date: Sat, 22 Feb 2025 11:33:50 +0100 Subject: [PATCH 2/4] feat: added events with rabbitmq, set up local docker --- apps/api/Dockerfile.local | 28 + apps/api/drizzle/meta/0000_snapshot.json | 858 +++++++++--------- apps/api/drizzle/meta/0001_snapshot.json | 858 +++++++++--------- apps/api/drizzle/meta/_journal.json | 38 +- apps/api/package.json | 2 + apps/api/src/database/index.ts | 1 - apps/api/src/events/index.ts | 124 +++ apps/api/src/index.ts | 9 +- apps/api/src/user/controllers/sign-up.ts | 9 + .../controllers/get-workspace-users.ts | 43 +- .../controllers/update-workspace-user.ts | 23 + apps/api/src/workspace-user/events/index.ts | 9 + apps/api/src/workspace-user/index.ts | 1 + apps/web/.gitignore | 24 - apps/web/Dockerfile.local | 28 + .../bottom-actions/sign-out-button.tsx | 5 +- apps/web/src/components/team/team-table.tsx | 4 +- .../use-get-pending-workspace-users.ts | 11 - .../queries/workspace/use-get-workspaces.ts | 8 +- .../teams/$workspaceId/_layout.members.tsx | 182 ++-- .../dashboard/teams/$workspaceId/_layout.tsx | 8 +- bun.lockb | Bin 211200 -> 214688 bytes compose.local.yml | 54 ++ 23 files changed, 1253 insertions(+), 1074 deletions(-) create mode 100644 apps/api/Dockerfile.local create mode 100644 apps/api/src/events/index.ts create mode 100644 apps/api/src/workspace-user/controllers/update-workspace-user.ts create mode 100644 apps/api/src/workspace-user/events/index.ts delete mode 100644 apps/web/.gitignore create mode 100644 apps/web/Dockerfile.local delete mode 100644 apps/web/src/hooks/queries/workspace-users/use-get-pending-workspace-users.ts create mode 100644 compose.local.yml diff --git a/apps/api/Dockerfile.local b/apps/api/Dockerfile.local new file mode 100644 index 0000000..4bdfb1e --- /dev/null +++ b/apps/api/Dockerfile.local @@ -0,0 +1,28 @@ +FROM --platform=$BUILDPLATFORM oven/bun:1 +WORKDIR /app + +RUN apt-get update && apt-get install -y \ + python3 \ + make \ + g++ \ + && rm -rf /var/lib/apt/lists/* + +COPY package.json . +COPY bun.lockb . +COPY apps/api/package.json ./apps/api/package.json +COPY packages/typescript-config/package.json ./packages/typescript-config/package.json + +RUN bun install --no-frozen-lockfile + +COPY packages/typescript-config ./packages/typescript-config +COPY apps/api ./apps/api + +RUN mkdir -p /app/apps/api/data && chmod 777 /app/apps/api/data + +RUN touch /app/apps/api/data/kaneo.db && chmod 666 /app/apps/api/data/kaneo.db + +RUN mkdir -p /app/apps/api/drizzle/meta && chmod -R 777 /app/apps/api/drizzle + +EXPOSE 1337 + +CMD ["bun", "--watch", "/app/apps/api/src/index.ts"] \ No newline at end of file diff --git a/apps/api/drizzle/meta/0000_snapshot.json b/apps/api/drizzle/meta/0000_snapshot.json index 868f9af..5b1b3e9 100644 --- a/apps/api/drizzle/meta/0000_snapshot.json +++ b/apps/api/drizzle/meta/0000_snapshot.json @@ -1,445 +1,415 @@ { - "version": "6", - "dialect": "sqlite", - "id": "b293274e-7c85-4fc2-b89c-16dc01c10984", - "prevId": "00000000-0000-0000-0000-000000000000", - "tables": { - "project": { - "name": "project", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "icon": { - "name": "icon", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'Layout'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'\"2025-02-18T21:46:28.196Z\"'" - } - }, - "indexes": {}, - "foreignKeys": { - "project_workspace_id_workspace_id_fk": { - "name": "project_workspace_id_workspace_id_fk", - "tableFrom": "project", - "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "session": { - "name": "session", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "session_user_id_user_id_fk": { - "name": "session_user_id_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "task": { - "name": "task", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "number": { - "name": "number", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 1 - }, - "assignee_email": { - "name": "assignee_email", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'to-do'" - }, - "priority": { - "name": "priority", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'low'" - }, - "due_date": { - "name": "due_date", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'\"2025-02-18T21:46:28.196Z\"'" - } - }, - "indexes": {}, - "foreignKeys": { - "task_project_id_project_id_fk": { - "name": "task_project_id_project_id_fk", - "tableFrom": "task", - "tableTo": "project", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - }, - "task_assignee_email_user_email_fk": { - "name": "task_assignee_email_user_email_fk", - "tableFrom": "task", - "tableTo": "user", - "columnsFrom": [ - "assignee_email" - ], - "columnsTo": [ - "email" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "user": { - "name": "user", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "password": { - "name": "password", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'\"2025-02-18T21:46:28.196Z\"'" - } - }, - "indexes": { - "user_email_unique": { - "name": "user_email_unique", - "columns": [ - "email" - ], - "isUnique": true - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "workspace": { - "name": "workspace", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "owner_email": { - "name": "owner_email", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'\"2025-02-18T21:46:28.196Z\"'" - } - }, - "indexes": {}, - "foreignKeys": { - "workspace_owner_email_user_email_fk": { - "name": "workspace_owner_email_user_email_fk", - "tableFrom": "workspace", - "tableTo": "user", - "columnsFrom": [ - "owner_email" - ], - "columnsTo": [ - "email" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "workspace_member": { - "name": "workspace_member", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "user_email": { - "name": "user_email", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "role": { - "name": "role", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'member'" - }, - "joined_at": { - "name": "joined_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'\"2025-02-18T21:46:28.196Z\"'" - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'active'" - } - }, - "indexes": {}, - "foreignKeys": { - "workspace_member_workspace_id_workspace_id_fk": { - "name": "workspace_member_workspace_id_workspace_id_fk", - "tableFrom": "workspace_member", - "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - }, - "workspace_member_user_email_user_email_fk": { - "name": "workspace_member_user_email_user_email_fk", - "tableFrom": "workspace_member", - "tableTo": "user", - "columnsFrom": [ - "user_email" - ], - "columnsTo": [ - "email" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - } - }, - "views": {}, - "enums": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "indexes": {} - } -} \ No newline at end of file + "version": "6", + "dialect": "sqlite", + "id": "b293274e-7c85-4fc2-b89c-16dc01c10984", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "project": { + "name": "project", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'Layout'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'\"2025-02-18T21:46:28.196Z\"'" + } + }, + "indexes": {}, + "foreignKeys": { + "project_workspace_id_workspace_id_fk": { + "name": "project_workspace_id_workspace_id_fk", + "tableFrom": "project", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "task": { + "name": "task", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "number": { + "name": "number", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 1 + }, + "assignee_email": { + "name": "assignee_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'to-do'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'low'" + }, + "due_date": { + "name": "due_date", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'\"2025-02-18T21:46:28.196Z\"'" + } + }, + "indexes": {}, + "foreignKeys": { + "task_project_id_project_id_fk": { + "name": "task_project_id_project_id_fk", + "tableFrom": "task", + "tableTo": "project", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "task_assignee_email_user_email_fk": { + "name": "task_assignee_email_user_email_fk", + "tableFrom": "task", + "tableTo": "user", + "columnsFrom": ["assignee_email"], + "columnsTo": ["email"], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'\"2025-02-18T21:46:28.196Z\"'" + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": ["email"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workspace": { + "name": "workspace", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "owner_email": { + "name": "owner_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'\"2025-02-18T21:46:28.196Z\"'" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_owner_email_user_email_fk": { + "name": "workspace_owner_email_user_email_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["owner_email"], + "columnsTo": ["email"], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workspace_member": { + "name": "workspace_member", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_email": { + "name": "user_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'member'" + }, + "joined_at": { + "name": "joined_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'\"2025-02-18T21:46:28.196Z\"'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_member_workspace_id_workspace_id_fk": { + "name": "workspace_member_workspace_id_workspace_id_fk", + "tableFrom": "workspace_member", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "workspace_member_user_email_user_email_fk": { + "name": "workspace_member_user_email_user_email_fk", + "tableFrom": "workspace_member", + "tableTo": "user", + "columnsFrom": ["user_email"], + "columnsTo": ["email"], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/apps/api/drizzle/meta/0001_snapshot.json b/apps/api/drizzle/meta/0001_snapshot.json index d07fb9b..377c4ed 100644 --- a/apps/api/drizzle/meta/0001_snapshot.json +++ b/apps/api/drizzle/meta/0001_snapshot.json @@ -1,445 +1,415 @@ { - "version": "6", - "dialect": "sqlite", - "id": "6058e5ae-5394-46de-b3a1-edd4f121bb0d", - "prevId": "b293274e-7c85-4fc2-b89c-16dc01c10984", - "tables": { - "project": { - "name": "project", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "icon": { - "name": "icon", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'Layout'" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'\"2025-02-18T21:46:39.855Z\"'" - } - }, - "indexes": {}, - "foreignKeys": { - "project_workspace_id_workspace_id_fk": { - "name": "project_workspace_id_workspace_id_fk", - "tableFrom": "project", - "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "session": { - "name": "session", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "session_user_id_user_id_fk": { - "name": "session_user_id_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "task": { - "name": "task", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "project_id": { - "name": "project_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "number": { - "name": "number", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": 1 - }, - "assignee_email": { - "name": "assignee_email", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'to-do'" - }, - "priority": { - "name": "priority", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'low'" - }, - "due_date": { - "name": "due_date", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'\"2025-02-18T21:46:39.856Z\"'" - } - }, - "indexes": {}, - "foreignKeys": { - "task_project_id_project_id_fk": { - "name": "task_project_id_project_id_fk", - "tableFrom": "task", - "tableTo": "project", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - }, - "task_assignee_email_user_email_fk": { - "name": "task_assignee_email_user_email_fk", - "tableFrom": "task", - "tableTo": "user", - "columnsFrom": [ - "assignee_email" - ], - "columnsTo": [ - "email" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "user": { - "name": "user", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "password": { - "name": "password", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'\"2025-02-18T21:46:39.855Z\"'" - } - }, - "indexes": { - "user_email_unique": { - "name": "user_email_unique", - "columns": [ - "email" - ], - "isUnique": true - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "workspace": { - "name": "workspace", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "owner_email": { - "name": "owner_email", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'\"2025-02-18T21:46:39.855Z\"'" - } - }, - "indexes": {}, - "foreignKeys": { - "workspace_owner_email_user_email_fk": { - "name": "workspace_owner_email_user_email_fk", - "tableFrom": "workspace", - "tableTo": "user", - "columnsFrom": [ - "owner_email" - ], - "columnsTo": [ - "email" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "workspace_member": { - "name": "workspace_member", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "user_email": { - "name": "user_email", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "role": { - "name": "role", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'member'" - }, - "joined_at": { - "name": "joined_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'\"2025-02-18T21:46:39.855Z\"'" - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'active'" - } - }, - "indexes": {}, - "foreignKeys": { - "workspace_member_workspace_id_workspace_id_fk": { - "name": "workspace_member_workspace_id_workspace_id_fk", - "tableFrom": "workspace_member", - "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - }, - "workspace_member_user_email_user_email_fk": { - "name": "workspace_member_user_email_user_email_fk", - "tableFrom": "workspace_member", - "tableTo": "user", - "columnsFrom": [ - "user_email" - ], - "columnsTo": [ - "email" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - } - }, - "views": {}, - "enums": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "indexes": {} - } -} \ No newline at end of file + "version": "6", + "dialect": "sqlite", + "id": "6058e5ae-5394-46de-b3a1-edd4f121bb0d", + "prevId": "b293274e-7c85-4fc2-b89c-16dc01c10984", + "tables": { + "project": { + "name": "project", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'Layout'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'\"2025-02-18T21:46:39.855Z\"'" + } + }, + "indexes": {}, + "foreignKeys": { + "project_workspace_id_workspace_id_fk": { + "name": "project_workspace_id_workspace_id_fk", + "tableFrom": "project", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "task": { + "name": "task", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "number": { + "name": "number", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 1 + }, + "assignee_email": { + "name": "assignee_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'to-do'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'low'" + }, + "due_date": { + "name": "due_date", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'\"2025-02-18T21:46:39.856Z\"'" + } + }, + "indexes": {}, + "foreignKeys": { + "task_project_id_project_id_fk": { + "name": "task_project_id_project_id_fk", + "tableFrom": "task", + "tableTo": "project", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "task_assignee_email_user_email_fk": { + "name": "task_assignee_email_user_email_fk", + "tableFrom": "task", + "tableTo": "user", + "columnsFrom": ["assignee_email"], + "columnsTo": ["email"], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'\"2025-02-18T21:46:39.855Z\"'" + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": ["email"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workspace": { + "name": "workspace", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "owner_email": { + "name": "owner_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'\"2025-02-18T21:46:39.855Z\"'" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_owner_email_user_email_fk": { + "name": "workspace_owner_email_user_email_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["owner_email"], + "columnsTo": ["email"], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workspace_member": { + "name": "workspace_member", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_email": { + "name": "user_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'member'" + }, + "joined_at": { + "name": "joined_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'\"2025-02-18T21:46:39.855Z\"'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_member_workspace_id_workspace_id_fk": { + "name": "workspace_member_workspace_id_workspace_id_fk", + "tableFrom": "workspace_member", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "workspace_member_user_email_user_email_fk": { + "name": "workspace_member_user_email_user_email_fk", + "tableFrom": "workspace_member", + "tableTo": "user", + "columnsFrom": ["user_email"], + "columnsTo": ["email"], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/apps/api/drizzle/meta/_journal.json b/apps/api/drizzle/meta/_journal.json index 7695752..f8eeafa 100644 --- a/apps/api/drizzle/meta/_journal.json +++ b/apps/api/drizzle/meta/_journal.json @@ -1,20 +1,20 @@ { - "version": "7", - "dialect": "sqlite", - "entries": [ - { - "idx": 0, - "version": "6", - "when": 1739915188203, - "tag": "0000_colorful_leech", - "breakpoints": true - }, - { - "idx": 1, - "version": "6", - "when": 1739915199865, - "tag": "0001_sparkling_jane_foster", - "breakpoints": true - } - ] -} \ No newline at end of file + "version": "7", + "dialect": "sqlite", + "entries": [ + { + "idx": 0, + "version": "6", + "when": 1739915188203, + "tag": "0000_colorful_leech", + "breakpoints": true + }, + { + "idx": 1, + "version": "6", + "when": 1739915199865, + "tag": "0001_sparkling_jane_foster", + "breakpoints": true + } + ] +} diff --git a/apps/api/package.json b/apps/api/package.json index e80bc69..771e1d6 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -14,6 +14,7 @@ "@oslojs/crypto": "^1.0.1", "@oslojs/encoding": "^1.1.0", "@paralleldrive/cuid2": "^2.2.2", + "amqplib": "^0.10.5", "better-sqlite3": "^11.8.1", "drizzle-kit": "^0.30.2", "drizzle-orm": "^0.39.1", @@ -21,6 +22,7 @@ "elysia": "1.2.14" }, "devDependencies": { + "@types/amqplib": "^0.10.6", "bun-types": "latest" }, "override": { diff --git a/apps/api/src/database/index.ts b/apps/api/src/database/index.ts index cd66b88..151c400 100644 --- a/apps/api/src/database/index.ts +++ b/apps/api/src/database/index.ts @@ -1,6 +1,5 @@ import { Database } from "bun:sqlite"; import { join } from "node:path"; -import { migrate } from "drizzle-orm/better-sqlite3/migrator"; import { drizzle } from "drizzle-orm/bun-sqlite"; import * as schema from "./schema"; diff --git a/apps/api/src/events/index.ts b/apps/api/src/events/index.ts new file mode 100644 index 0000000..9bcdb3f --- /dev/null +++ b/apps/api/src/events/index.ts @@ -0,0 +1,124 @@ +import amqp, { type Channel, type Connection } from "amqplib"; + +const EXCHANGE_NAME = "kaneo.events"; +const QUEUE_NAME = "kaneo.events.queue"; + +let connection: Connection | null = null; +let channel: Channel | null = null; + +export type EventPayload = { + type: string; + data: T; + timestamp: string; +}; + +async function createConnection(): Promise { + return amqp.connect(process.env.RABBITMQ_URL || "amqp://localhost:5672"); +} + +async function createChannel(conn: Connection): Promise { + const channel = await conn.createChannel(); + await channel.assertExchange(EXCHANGE_NAME, "topic", { durable: true }); + await channel.assertQueue(QUEUE_NAME, { durable: true }); + return channel; +} + +async function getChannel(): Promise { + if (!connection || !channel) { + connection = await createConnection(); + channel = await createChannel(connection); + + connection.on("close", async () => { + console.error("RabbitMQ connection closed. Attempting to reconnect..."); + connection = null; + channel = null; + await initializeEventBus(); + }); + } + return channel; +} + +export async function initializeEventBus(): Promise { + try { + await getChannel(); + } catch (error) { + console.error("Failed to initialize RabbitMQ:", error); + throw error; + } +} + +export async function shutdownEventBus(): Promise { + if (channel) { + await channel.close(); + channel = null; + } + if (connection) { + await connection.close(); + connection = null; + } +} + +export async function publishEvent( + eventType: string, + data: unknown, +): Promise { + const channel = await getChannel(); + + const payload: EventPayload = { + type: eventType, + data, + timestamp: new Date().toISOString(), + }; + + try { + channel.publish( + EXCHANGE_NAME, + eventType, + Buffer.from(JSON.stringify(payload)), + { persistent: true }, + ); + } catch (error) { + console.error("Failed to publish event:", error); + throw error; + } +} + +export async function subscribeToEvent( + eventType: string, + handler: (data: T) => Promise, +): Promise { + const channel = await getChannel(); + + try { + await channel.bindQueue(QUEUE_NAME, EXCHANGE_NAME, eventType); + + await channel.consume( + QUEUE_NAME, + async (msg) => { + if (!msg) return; + + try { + const payload = JSON.parse(msg.content.toString()) as EventPayload; + + if (payload.type === eventType) { + await handler(payload.data); + channel.ack(msg); + } + } catch (error) { + console.error(`Error processing event ${eventType}:`, error); + channel.nack(msg, false, true); + } + }, + { noAck: false }, + ); + } catch (error) { + console.error("Failed to subscribe to event:", error); + throw error; + } +} + +initializeEventBus().catch(console.error); + +process.on("SIGTERM", () => { + shutdownEventBus().catch(console.error); +}); diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 2408a69..dfa414f 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -1,5 +1,6 @@ +import path from "node:path"; import { cors } from "@elysiajs/cors"; -import { migrate } from "drizzle-orm/better-sqlite3/migrator"; +import { migrate } from "drizzle-orm/bun-sqlite/migrator"; import { Elysia } from "elysia"; import db from "./database"; import project from "./project"; @@ -60,8 +61,8 @@ const app = new Elysia() export type App = typeof app; -console.log("Migrating database..."); -migrate(db, { migrationsFolder: "drizzle" }); -console.log("Database migrated"); +migrate(db, { + migrationsFolder: path.join(__dirname, "../drizzle"), +}); console.log(`🏃 Kaneo is running at ${app.server?.url}`); diff --git a/apps/api/src/user/controllers/sign-up.ts b/apps/api/src/user/controllers/sign-up.ts index 6e62f00..ea6518c 100644 --- a/apps/api/src/user/controllers/sign-up.ts +++ b/apps/api/src/user/controllers/sign-up.ts @@ -1,6 +1,7 @@ import type { Static } from "elysia"; import db from "../../database"; import { userTable } from "../../database/schema"; +import { publishEvent } from "../../events"; import type { signUpUserSchema } from "../db/queries"; import { UserErrors } from "../errors"; @@ -28,6 +29,14 @@ async function signUp({ email, name, password }: SignUpArgs) { .returning() ).at(0); + if (!user) { + throw new Error(UserErrors.NotFound); + } + + publishEvent("user.signed_up", { + email: user.email, + }); + return user; } diff --git a/apps/api/src/workspace-user/controllers/get-workspace-users.ts b/apps/api/src/workspace-user/controllers/get-workspace-users.ts index fbd4802..2bf0fc0 100644 --- a/apps/api/src/workspace-user/controllers/get-workspace-users.ts +++ b/apps/api/src/workspace-user/controllers/get-workspace-users.ts @@ -1,10 +1,6 @@ -import { and, asc, desc, eq, not } from "drizzle-orm"; +import { asc, eq } from "drizzle-orm"; import db from "../../database"; -import { - userTable, - workspaceTable, - workspaceUserTable, -} from "../../database/schema"; +import { userTable, workspaceUserTable } from "../../database/schema"; function getWorkspaceUsers({ workspaceId }: { workspaceId: string }) { return db @@ -15,39 +11,10 @@ function getWorkspaceUsers({ workspaceId }: { workspaceId: string }) { status: workspaceUserTable.status, role: workspaceUserTable.role, }) - .from(workspaceTable) - .innerJoin( - workspaceUserTable, - eq(workspaceTable.id, workspaceUserTable.workspaceId), - ) + .from(workspaceUserTable) .leftJoin(userTable, eq(workspaceUserTable.userEmail, userTable.email)) - .where( - and( - eq(workspaceTable.id, workspaceId), - not(eq(workspaceUserTable.userEmail, workspaceTable.ownerEmail)), - ), - ) - .unionAll( - db - .select({ - userEmail: workspaceUserTable.userEmail, - userName: userTable.name, - joinedAt: workspaceUserTable.joinedAt, - status: workspaceUserTable.status, - role: workspaceUserTable.role, - }) - .from(workspaceTable) - .where(eq(workspaceTable.id, workspaceId)) - .innerJoin( - workspaceUserTable, - and( - eq(workspaceTable.id, workspaceUserTable.workspaceId), - eq(workspaceUserTable.userEmail, workspaceTable.ownerEmail), - ), - ) - .leftJoin(userTable, eq(workspaceUserTable.userEmail, userTable.email)), - ) - .orderBy(asc(workspaceUserTable.joinedAt)); + .where(eq(workspaceUserTable.workspaceId, workspaceId)) + .orderBy(asc(workspaceUserTable.status)); } export default getWorkspaceUsers; diff --git a/apps/api/src/workspace-user/controllers/update-workspace-user.ts b/apps/api/src/workspace-user/controllers/update-workspace-user.ts new file mode 100644 index 0000000..69dc0d0 --- /dev/null +++ b/apps/api/src/workspace-user/controllers/update-workspace-user.ts @@ -0,0 +1,23 @@ +import { eq } from "drizzle-orm"; +import db from "../../database"; +import { workspaceUserTable } from "../../database/schema"; + +async function upsertWorkspaceUser(data: { + email: string; + status: string; +}) { + const workspaceUser = await db.query.workspaceUserTable.findFirst({ + where: eq(workspaceUserTable.userEmail, data.email), + }); + + if (!workspaceUser) { + return; + } + + await db + .update(workspaceUserTable) + .set({ status: data.status }) + .where(eq(workspaceUserTable.id, workspaceUser.id)); +} + +export default upsertWorkspaceUser; diff --git a/apps/api/src/workspace-user/events/index.ts b/apps/api/src/workspace-user/events/index.ts new file mode 100644 index 0000000..ca64c95 --- /dev/null +++ b/apps/api/src/workspace-user/events/index.ts @@ -0,0 +1,9 @@ +import { subscribeToEvent } from "../../events"; +import updateWorkspaceUser from "../controllers/update-workspace-user"; + +subscribeToEvent("user.signed_up", async (data: { email: string }) => { + updateWorkspaceUser({ + email: data.email, + status: "active", + }); +}); diff --git a/apps/api/src/workspace-user/index.ts b/apps/api/src/workspace-user/index.ts index f6d8abd..cde407a 100644 --- a/apps/api/src/workspace-user/index.ts +++ b/apps/api/src/workspace-user/index.ts @@ -2,6 +2,7 @@ import Elysia, { t } from "elysia"; import deleteWorkspaceUser from "./controllers/delete-workspace-user"; import getWorkspaceUsers from "./controllers/get-workspace-users"; import inviteWorkspaceUser from "./controllers/invite-workspace-user"; +import "./events"; const workspaceUser = new Elysia({ prefix: "/workspace-user" }) .get( diff --git a/apps/web/.gitignore b/apps/web/.gitignore deleted file mode 100644 index a547bf3..0000000 --- a/apps/web/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/apps/web/Dockerfile.local b/apps/web/Dockerfile.local new file mode 100644 index 0000000..61a7f85 --- /dev/null +++ b/apps/web/Dockerfile.local @@ -0,0 +1,28 @@ +FROM oven/bun:1 + +WORKDIR /app + +RUN apt-get update && apt-get install -y \ + python3 \ + make \ + g++ \ + build-essential \ + && rm -rf /var/lib/apt/lists/* + +COPY package.json bun.lockb ./ +COPY apps/web/package.json ./apps/web/package.json +COPY apps/api/package.json ./apps/api/package.json +COPY packages/typescript-config/package.json ./packages/typescript-config/package.json +COPY packages/libs/package.json ./packages/libs/package.json + +COPY packages/typescript-config ./packages/typescript-config +COPY packages/libs ./packages/libs + +RUN bun install + +COPY apps/web ./apps/web + +EXPOSE 5173 + +WORKDIR /app/apps/web +CMD ["bun", "run", "dev", "--host"] \ No newline at end of file diff --git a/apps/web/src/components/common/sidebar/sections/bottom-actions/sign-out-button.tsx b/apps/web/src/components/common/sidebar/sections/bottom-actions/sign-out-button.tsx index 003c25a..a812611 100644 --- a/apps/web/src/components/common/sidebar/sections/bottom-actions/sign-out-button.tsx +++ b/apps/web/src/components/common/sidebar/sections/bottom-actions/sign-out-button.tsx @@ -16,10 +16,7 @@ function SignOutButton() { const handleSignOut = async () => { await mutateAsync(); - queryClient.invalidateQueries({ - queryKey: ["me"], - type: "all", - }); + queryClient.clear(); setUser(null); setProject(undefined); setWorkspace(undefined); diff --git a/apps/web/src/components/team/team-table.tsx b/apps/web/src/components/team/team-table.tsx index c91cf5e..4fb5c0e 100644 --- a/apps/web/src/components/team/team-table.tsx +++ b/apps/web/src/components/team/team-table.tsx @@ -31,7 +31,9 @@ function TeamTable() {
- {user.userName.charAt(0)} + + {user.userName?.charAt(0) ?? "U"} +
diff --git a/apps/web/src/hooks/queries/workspace-users/use-get-pending-workspace-users.ts b/apps/web/src/hooks/queries/workspace-users/use-get-pending-workspace-users.ts deleted file mode 100644 index 5bd14d4..0000000 --- a/apps/web/src/hooks/queries/workspace-users/use-get-pending-workspace-users.ts +++ /dev/null @@ -1,11 +0,0 @@ -import getPendingWorkspaceUsers from "@/fetchers/workspace-user/get-pending-workspace-users"; -import { useQuery } from "@tanstack/react-query"; - -function useGetPendingWorkspaceUsers({ workspaceId }: { workspaceId: string }) { - return useQuery({ - queryKey: ["pending-workspace-users", workspaceId], - queryFn: () => getPendingWorkspaceUsers({ workspaceId }), - }); -} - -export default useGetPendingWorkspaceUsers; diff --git a/apps/web/src/hooks/queries/workspace/use-get-workspaces.ts b/apps/web/src/hooks/queries/workspace/use-get-workspaces.ts index c7badf1..de81e14 100644 --- a/apps/web/src/hooks/queries/workspace/use-get-workspaces.ts +++ b/apps/web/src/hooks/queries/workspace/use-get-workspaces.ts @@ -1,12 +1,14 @@ +import useAuth from "@/components/providers/auth-provider/hooks/use-auth"; import getWorkspaces from "@/fetchers/workspace/get-workspaces"; import { useQuery } from "@tanstack/react-query"; function useGetWorkspaces() { + const { user } = useAuth(); + return useQuery({ queryFn: () => getWorkspaces(), - queryKey: ["workspaces"], - refetchOnWindowFocus: false, - refetchOnMount: false, + queryKey: ["workspaces", user?.email], + enabled: !!user?.email, }); } diff --git a/apps/web/src/routes/dashboard/teams/$workspaceId/_layout.members.tsx b/apps/web/src/routes/dashboard/teams/$workspaceId/_layout.members.tsx index 1d10ad3..746bbbc 100644 --- a/apps/web/src/routes/dashboard/teams/$workspaceId/_layout.members.tsx +++ b/apps/web/src/routes/dashboard/teams/$workspaceId/_layout.members.tsx @@ -1,3 +1,4 @@ +import useAuth from "@/components/providers/auth-provider/hooks/use-auth"; import DeleteTeamMemberModal from "@/components/team/delete-team-member-modal"; import InviteTeamMemberModal from "@/components/team/invite-team-member-modal"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; @@ -31,12 +32,30 @@ const getStatusText = (status: "active" | "pending") => pending: "Pending", })[status]; +type WorkspaceUser = { + userEmail: string; + userName: string | null; + joinedAt: Date; + status: string; + role: string; +}; + function RouteComponent() { const { workspaceId } = Route.useParams(); const { data: users } = useGetWorkspaceUsers({ workspaceId }); const [isInviteTeamMemberModalOpen, setIsInviteTeamMemberModalOpen] = useState(false); const [isRemoveMemberModalOpen, setIsRemoveMemberModalOpen] = useState(false); + const [selectedMember, setSelectedMember] = useState( + null, + ); + + const { user } = useAuth(); + + const isOwner = users?.some( + (workspaceUser) => + workspaceUser.userEmail === user?.email && workspaceUser.role === "owner", + ); return (
- - - - - - - - - - - {users?.map((member) => ( - - - - - - +
+
- Email - - Role - - Status - - Date - -
-
- - - {member.userEmail.charAt(0)} - - - - {member.userEmail} - -
-
- - {member.role.charAt(0).toUpperCase() + - member.role.slice(1).toLowerCase()} - - -
- {getStatusIcon(member.status as "active" | "pending")} - - {getStatusText(member.status as "active" | "pending")} - -
-
- {member.joinedAt && - new Date(member.joinedAt).toLocaleDateString("en-US", { - month: "short", - day: "numeric", - year: "numeric", - })} - - -
+ + + + + + + - ))} - -
+ Email + + Role + + Status + + Date +
+ + + {users?.map((member) => ( + + +
+ + + {member.userEmail.charAt(0)} + + + + {member.userEmail} + +
+ + + + {member.role.charAt(0).toUpperCase() + + member.role.slice(1).toLowerCase()} + + + +
+ {getStatusIcon(member.status as "active" | "pending")} + + {getStatusText(member.status as "active" | "pending")} + +
+ + + {member.joinedAt && + new Date(member.joinedAt).toLocaleDateString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + })} + + + {member.role !== "owner" && isOwner && ( + + )} + + + ))} + + - {users?.length === 0 && ( -
-

- No team members found -

-
- )} + {users?.length === 0 && ( +
+

+ No team members found +

+
+ )} +
setIsRemoveMemberModalOpen(false)} /> diff --git a/apps/web/src/routes/dashboard/teams/$workspaceId/_layout.tsx b/apps/web/src/routes/dashboard/teams/$workspaceId/_layout.tsx index fd244ab..c543244 100644 --- a/apps/web/src/routes/dashboard/teams/$workspaceId/_layout.tsx +++ b/apps/web/src/routes/dashboard/teams/$workspaceId/_layout.tsx @@ -9,7 +9,7 @@ import { useRouterState, } from "@tanstack/react-router"; import { AnimatePresence, motion } from "framer-motion"; -import { Shield, UserPlus, Users } from "lucide-react"; +import { Shield, Users } from "lucide-react"; import { Menu } from "lucide-react"; import { useEffect, useState } from "react"; @@ -138,12 +138,14 @@ function RouteComponent() { - +
+ +
); diff --git a/bun.lockb b/bun.lockb index a917d0c853a5ecaa87d78e03a43141a0db67312a..5012fcfd22f5fcb4f42e036e0ba5733de792bdff 100755 GIT binary patch delta 9275 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 EKPW<_bN~PV diff --git a/compose.local.yml b/compose.local.yml new file mode 100644 index 0000000..03b3162 --- /dev/null +++ b/compose.local.yml @@ -0,0 +1,54 @@ +services: + backend: + build: + context: . + dockerfile: apps/api/Dockerfile.local + environment: + JWT_ACCESS: "change_me" + DB_PATH: "/app/apps/api/data/kaneo.db" + NODE_ENV: "development" + RABBITMQ_URL: "amqp://guest:guest@rabbitmq:5672" + ports: + - 1337:1337 + volumes: + - ./apps/api:/app/apps/api + - sqlite_data:/app/apps/api/data + depends_on: + rabbitmq: + condition: service_healthy + restart: unless-stopped + + frontend: + build: + context: . + dockerfile: apps/web/Dockerfile.local + environment: + KANEO_API_URL: "http://localhost:1337" + NODE_ENV: "development" + ports: + - 5173:5173 + volumes: + - ./apps/web:/app/apps/web + depends_on: + - backend + restart: unless-stopped + + rabbitmq: + image: rabbitmq:3-management + ports: + - "5672:5672" + - "15672:15672" + environment: + - RABBITMQ_DEFAULT_USER=guest + - RABBITMQ_DEFAULT_PASS=guest + volumes: + - rabbitmq_data:/var/lib/rabbitmq + healthcheck: + test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"] + interval: 10s + timeout: 5s + retries: 5 + +volumes: + sqlite_data: + rabbitmq_data: \ No newline at end of file From 75ce3780dcebdb8b6a52be6d0989ff66d14c55ed Mon Sep 17 00:00:00 2001 From: Andrej Date: Sat, 22 Feb 2025 21:13:29 +0100 Subject: [PATCH 3/4] style: cleaning up code --- .../projects/create-project-modal.tsx | 7 - .../team/delete-team-member-modal.tsx | 2 - .../web/src/components/team/members-table.tsx | 91 +++++++++++++ apps/web/src/components/team/team-table.tsx | 72 ---------- apps/web/src/lib/status.tsx | 21 +++ .../teams/$workspaceId/_layout.members.tsx | 125 +----------------- apps/web/src/types/workspace-user/index.ts | 9 ++ bun.lockb | Bin 214688 -> 214224 bytes compose.yml | 25 ++++ package.json | 4 +- 10 files changed, 155 insertions(+), 201 deletions(-) create mode 100644 apps/web/src/components/team/members-table.tsx delete mode 100644 apps/web/src/components/team/team-table.tsx create mode 100644 apps/web/src/lib/status.tsx create mode 100644 apps/web/src/types/workspace-user/index.ts diff --git a/apps/web/src/components/common/sidebar/sections/projects/create-project-modal.tsx b/apps/web/src/components/common/sidebar/sections/projects/create-project-modal.tsx index 123e99d..54ccf40 100644 --- a/apps/web/src/components/common/sidebar/sections/projects/create-project-modal.tsx +++ b/apps/web/src/components/common/sidebar/sections/projects/create-project-modal.tsx @@ -107,13 +107,6 @@ function CreateProjectModal({ open, onClose }: CreateProjectModalProps) { pattern="[A-Z0-9]+" required /> -

This key will be used for ticket IDs (e.g., ABC-123) diff --git a/apps/web/src/components/team/delete-team-member-modal.tsx b/apps/web/src/components/team/delete-team-member-modal.tsx index dadcb73..2aa0d07 100644 --- a/apps/web/src/components/team/delete-team-member-modal.tsx +++ b/apps/web/src/components/team/delete-team-member-modal.tsx @@ -17,8 +17,6 @@ function DeleteTeamMemberModal({ const { mutateAsync: deleteWorkspaceUser } = useDeleteWorkspaceUser(); const queryClient = useQueryClient(); - console.log({ workspaceId, userEmail }); - const onRemoveMember = async () => { await deleteWorkspaceUser({ workspaceId: workspaceId, diff --git a/apps/web/src/components/team/members-table.tsx b/apps/web/src/components/team/members-table.tsx new file mode 100644 index 0000000..e287cdf --- /dev/null +++ b/apps/web/src/components/team/members-table.tsx @@ -0,0 +1,91 @@ +import { getStatusIcon, getStatusText } from "@/lib/status"; +import type WorkspaceUser from "@/types/workspace-user"; +import { Avatar, AvatarFallback } from "../ui/avatar"; + +type MembersTableProps = { + users: WorkspaceUser[]; +}; + +function MembersTable({ users }: MembersTableProps) { + return ( + + + + + + + + + + + {users?.map((member) => ( + + + + + + {/* TODO: Implement delete member */} + {/* */} + + ))} + +
+ Email + + Role + + Status + + Date + +
+
+ + + {member.userEmail.charAt(0)} + + + + {member.userEmail} + +
+
+ + {member.role.charAt(0).toUpperCase() + + member.role.slice(1).toLowerCase()} + + +
+ {getStatusIcon(member.status as "active" | "pending")} + + {getStatusText(member.status as "active" | "pending")} + +
+
+ {member.joinedAt && + new Date(member.joinedAt).toLocaleDateString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + })} + + {member.role !== "owner" && isOwner && ( + + )} +
+ ); +} + +export default MembersTable; diff --git a/apps/web/src/components/team/team-table.tsx b/apps/web/src/components/team/team-table.tsx deleted file mode 100644 index 4fb5c0e..0000000 --- a/apps/web/src/components/team/team-table.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import useGetWorkspaceUsers from "@/hooks/queries/workspace-users/use-get-workspace-users"; -import { Route } from "@/routes/dashboard/teams/$workspaceId/_layout.roles"; -import { MoreHorizontal } from "lucide-react"; -import { Avatar, AvatarFallback } from "../ui/avatar"; - -function TeamTable() { - const { workspaceId } = Route.useParams(); - const { data: users } = useGetWorkspaceUsers({ workspaceId }); - - return ( - - - - - - - - - - {users?.map((user) => ( - - - - - - - ))} - -
- Name - - Role - - Joined -
-
- - - {user.userName?.charAt(0) ?? "U"} - - -
-
- {user.userName} -
-
-
-
- - Admin - - - {new Date(user.joinedAt).toLocaleDateString("en-US", { - year: "numeric", - month: "short", - day: "numeric", - })} - - -
- ); -} - -export default TeamTable; diff --git a/apps/web/src/lib/status.tsx b/apps/web/src/lib/status.tsx new file mode 100644 index 0000000..daa0e73 --- /dev/null +++ b/apps/web/src/lib/status.tsx @@ -0,0 +1,21 @@ +import { CheckCircle2, Clock } from "lucide-react"; + +export function getStatusIcon(status: "active" | "pending") { + switch (status) { + case "active": + return ( + + ); + case "pending": + return ; + } +} + +export function getStatusText(status: "active" | "pending") { + switch (status) { + case "active": + return "Active"; + case "pending": + return "Pending"; + } +} diff --git a/apps/web/src/routes/dashboard/teams/$workspaceId/_layout.members.tsx b/apps/web/src/routes/dashboard/teams/$workspaceId/_layout.members.tsx index 746bbbc..0016174 100644 --- a/apps/web/src/routes/dashboard/teams/$workspaceId/_layout.members.tsx +++ b/apps/web/src/routes/dashboard/teams/$workspaceId/_layout.members.tsx @@ -1,12 +1,10 @@ -import useAuth from "@/components/providers/auth-provider/hooks/use-auth"; -import DeleteTeamMemberModal from "@/components/team/delete-team-member-modal"; import InviteTeamMemberModal from "@/components/team/invite-team-member-modal"; -import { Avatar, AvatarFallback } from "@/components/ui/avatar"; +import MembersTable from "@/components/team/members-table"; import { Button } from "@/components/ui/button"; import useGetWorkspaceUsers from "@/hooks/queries/workspace-users/use-get-workspace-users"; import { createFileRoute } from "@tanstack/react-router"; import { motion } from "framer-motion"; -import { CheckCircle2, Clock, MoreHorizontal, UserPlus } from "lucide-react"; +import { UserPlus } from "lucide-react"; import { useState } from "react"; export const Route = createFileRoute( @@ -15,47 +13,11 @@ export const Route = createFileRoute( component: RouteComponent, }); -const getStatusIcon = (status: "active" | "pending") => { - switch (status) { - case "active": - return ( - - ); - case "pending": - return ; - } -}; - -const getStatusText = (status: "active" | "pending") => - ({ - active: "Active", - pending: "Pending", - })[status]; - -type WorkspaceUser = { - userEmail: string; - userName: string | null; - joinedAt: Date; - status: string; - role: string; -}; - function RouteComponent() { const { workspaceId } = Route.useParams(); const { data: users } = useGetWorkspaceUsers({ workspaceId }); const [isInviteTeamMemberModalOpen, setIsInviteTeamMemberModalOpen] = useState(false); - const [isRemoveMemberModalOpen, setIsRemoveMemberModalOpen] = useState(false); - const [selectedMember, setSelectedMember] = useState( - null, - ); - - const { user } = useAuth(); - - const isOwner = users?.some( - (workspaceUser) => - workspaceUser.userEmail === user?.email && workspaceUser.role === "owner", - ); return (

- - - - - - - - - - - {users?.map((member) => ( - - - - - - - - ))} - -
- Email - - Role - - Status - - Date - -
-
- - - {member.userEmail.charAt(0)} - - - - {member.userEmail} - -
-
- - {member.role.charAt(0).toUpperCase() + - member.role.slice(1).toLowerCase()} - - -
- {getStatusIcon(member.status as "active" | "pending")} - - {getStatusText(member.status as "active" | "pending")} - -
-
- {member.joinedAt && - new Date(member.joinedAt).toLocaleDateString("en-US", { - month: "short", - day: "numeric", - year: "numeric", - })} - - {member.role !== "owner" && isOwner && ( - - )} -
+ {users?.length === 0 && (
@@ -166,11 +53,13 @@ function RouteComponent() { )}
- setIsRemoveMemberModalOpen(false)} - /> + /> */} 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 delta 9273 zcmeHNdt4RO+Mb!sR)&j!VoE@YB4Q?jC>swVUP(0bE}|i9FUeK}@qPipyND<_o?f6Z zws%u3yo8t3u=3Q&e znwhnS&$3-ha$WO+T3vYkmFd$>kM;$OSl49!=|tn%>Tiy&IkqJrCwxY3&9wwi_h^H_ zNB#WRCQ@&gvY92$nK5(W3qsbFOhE_$?*}&r7w9}4+zkE@uphX+&Ys|=@E@W+^LcO+ za5?w|a1q!S91ivXcU~w6?%=8BN#;?>V+DfmEC`udfe7G&SAqq>1zfaP5E_Fs!QS9e zU~Z@zm`@Y{_5r(sy})-DY31j^9KRp@Ja|305x5YH6J^0x`RFo1s0V*+@~kNqb00y7 z&CxRK4R%FASFj8ootiqqGFA|@z}h%l`93t{#PPXW{6fX-+Ni57de90%a6@S?uroMP z=iPZ)S`G6xo(AUezY6BoK3B?I56M$IlZhNYE za+}wu2*S@O=eQftxviYFS`N3ttbeVuB_(O>WXseknechYCo5LBwwclfz28ZxGg2lC z!lY>vCt=KlhI(s38@2KA1~)*hPr=;k$f}4`>*IMbnT6WA3gl-TUD3wtrc`LlXY2J$_`CEA1;W;Sg zK$AQIYbMI>Ib;dKIJ-9ip4sl5_>I?Qo**RH%a+57w|iB;@uKGo!U%hr^*3HMyf}N= zkOf#8c5fTJA$Cv1B9F9r;;TyGyeN4sv@mFrVhu5g7Zk7gQF0@!(XP;pin*0Z9Htb` zk23DS`i@hI=SLWwmmq7^8V_rVZ7p!=-)MFVk zW=l+0;uiJm_Iz#;J;9A{CVXGT=Bby2()M~O*F+~O$t9o#y2mRuC8$t2%`$8iG3 z$t{-)LVK;2zeyaS#4U*u^OV9RQSw=oXmOSPCfNlSSqEF|a&LIG=gL(Im-hQzmzd&` z?GyP&J2nanMLjh-DJkZ$trhn?*#Tm-QXCQ?URDaTqr`zq1^%8;yp~1DFX5&YgIo=Y zUu%=NL@9(;1+A0X>&gI=++vj=bXI#~jWroZ!wXm9mv?f2(NBrbi7@^EtFLOA6q~is zZk@4PFRT`XK5Co=R*Y)xuv<^b}Nc(+G4wP z5>`(&u6_YqmvcKgs0MfAEt@4)Dqi_fvVWl<^wLJg-(;KyFI*{J)yV-yKczT7!dMNf zuWH2<*{nD0)-}5o{3iFCYs`TaqgqGpmg`$KZK&OP+iu;kTOq}^xY>5=2&|XY_MFz( ztO0hbz;0cI)l-f0f154e)twwvgOl2Bw;tFmIlKg6=)1Gx*WD!N!sC&stFDRGRzc_j zPf+}Dtxt#78J>~v1@bOkw^qS!eUGio}JAS5cJX2hjLzR5@4-RzCA{l&hh1Q|njdj-56YCW9T=(`DND8;QKaGaZg@fPM8&U@ zNxlY;2T~nY$#1iAaZP7=_-1Y7F?N9_<63yH(!F#sR2_)BaCs;t^9aH9JGCdZ&s&9f)r zI35aj!LG<_q42Qcw5?6X2k;_!G#V(u>jUM;?b_7i@rt=hgQxYMA4gl1iuFNb7EZh+4nrRdSVV*&IPAmZXz<(R;0A8o-8+6{J^A<24zvk!QAJX+d zz?o`8f1twubG&wbHFF6~A4U%vfiLJi`O`V`PdE{G-?g~{F7eJ%;ppP;RK%Q`hQ|Bf2PGNsk?n+I3Xkd`5&0i zQ%5iV-&j;m?rxOHt-I*a|A{#b|Hw+Gr9~jp=AbGz3lTDBI%~>$Y5OD5fk?(TDFUiLb*7Vx{aq|4~ z_?}`H_bwlP^x@9M5$7td9qj5n;j&}=v@(zSeHyt{U9TwZloOS;>8nje(Zh;&T)S8K z_b}txjlV=6*k9ySJ$j&L#mOyg-&!(=E)NkKQ}Gb7k7%W;Az~~Q3>96e?@$QqsBkER zUa=6Kv9N(+Vj(EEG?NAW{_z1v4P@odLl>g)<=ZnhD_<3r32W3E?pd zr86Par6(+Go&_O(76h5L%z`j%HU!t%5S%G)HU!5x5Du{5O7a{Cdsvt_2Z9^zWg#gY zf^Rwmk8(@8nAt?O{i>Q&e(z0jgs7DGd171PAV!uCC>9eO#EkMXt9Zv?4907-E5B#Y zL5NHFOB+SMv8z4~amsVyN4X&QyHTvO6iNBcQuDqnG4%HxVHe&L_*H@ZSY6z$Yy8%L zO|p8tuWS7Bg-xw`l#}QpHHh4!7uAJ`J(7Ctg~p+d0QNTOaZrzw;bBLk9*1ZEH}Oxs z$Qfdop2ZPegXi z8=`Ap(>*kiS*aJ{msi08n~!gF%@g)?UAv}h4WXrS5RU7*#_v;K>zRG4Yn<`7y7rx} zHHNlc*S^=Zdi-0va11$dA8t^-D{`vR(+|zUNhK6Eip}(6pNdIV59#l_SCiZh@_fL~7Q0*-(T@Mfd}umWp=w}BF1K9CM%0CUTY?$Qh~(-rac04Km1;MZo}6I=qm z00<&os=A8&|00nP!R0%w7bfX{&wz(0YLz{kLO;7j0?0Uuw! zfN>f)0vrQA1C9b`faAc2<-YZ$cf}Ufuz8OlB0&jo0C;au1Ka?rfQ@vofwak2{cRe> zyk)rs+y?Fdye-{KyFI0-0N#c0fZqqUW3;bdeZ;yB) zc}f(3Col;yi9i|jeZUT2FHj2X2X+Fy^W6n(2R;A}0=t0&!27^Ez`H;>uoc(?ya#Lp z7UCSaqCg4HNpbafn{~I5c0nWt5-x~<@*B@dyIhBPKy(KROv6JzF2b@=0AW#z&hYQdb}RI8Q1`90yY8%X<7>@ z*6B2g@q?srL8LEQNW(LqqUa*<3-APZ3CxBPRec(30H;zjH-&9!VmBcp= zj=ur$%H|c%cY?2V<~s%7LAVaDa$eQEdJ&VU2JsT*rOSofz+K(G1HK150v-aK*aPrH za5Y%M(22mmVDs&)2FyNpl9#R#kO4;>eA{;hRw1sQ%G9o! zQYvz*(;+CNeNb?@k4d`UT?{0*Ig(cg+XuzuRuAOX0o5dlBM}pXU)pS+GH(yL*3iAc z?MQ7&5yhjbx9!{HsjBm(i`G8MbrAKM!gnV zuzd_IbRFt-xYvXFwE?!zrT!rUg1Qgix3adRfZC*^ZQJ+N;}IE&!PAxustve8=5(pA z*qPo%oJe#$T?!Qo=^58rzexL9Xo|DEe$N^dRAaNltr9&KG``ULiVZoQD>ii@&Lt0u(KhT3LDb(Bc{qQ@g z7jbl^*N9vl>kF%WFugjWVw z)3etM*r%27bYg=uPYO2RS7<89l%D^+gCO=IkNHR-mRf^+d{(N@%q-)$-V?4mx32UQ zJ4f=Z8^3X`q{R7B;|_fhfwzXNo;`alPxo|4;s|Ya%qXdcl4kuIJ$!ah8mX0pV1cDl zDH`{luQwj!CamfoP?lC3P(+oS#7=t3aUUWMFCkf339qfJyY90V9EYL8ago|Cki5cf z=mGA16X@Q+H4&cTi)u0M#d^uw$0@-M$DW^9TT(xd<}Q%h`FQ84?^#*XEZfh$+%x)q zZA4%mome0>_6|pc8%}@V#O`Rv!srpT5pAgkr~hx)Lv6sz<)I5DgXo=)Cfw14#kY6Z z&I?bwN#e-RpbmU#S!uu`^mI2(SR{o?4fCmFk<`xH_9_1j_pGj=@!NYMJ{a-sLWIxh z7RO&Lue(^9CyB}B*-Ir{_8BDRKrSS2koWl#En}ab7oB}guAYtRxQ?+ZgzMBVM+)`% z838;s2b~7QH*4MYnchAEj8 ztVBkGDPbknnC;tt*7Td7$Mz1Z=GCm;+=U1#U5P~cQQJIF0#&lNj-GFh81pmn{P8XoFx`_}-!OKnHC9O?JHB+kXN1xr!rPZ?;>$M(Mfy&e3kl85`o)tfVTExTd zct646)M;9H=cXZ)*i!N;KekFr$S@!k60b!XpQvmLE+sD%_y;(p{Y}WE)MHS%eoo<<+QY?7PPr12L;fc?g+Z8 Pi|)}J Date: Sat, 22 Feb 2025 21:15:15 +0100 Subject: [PATCH 4/4] ci: updating changelog --- CHANGELOG.md | 72 ---------------------------------------------------- 1 file changed, 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1f4eab..3c65331 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,75 +1,3 @@ -# 0.7.0 (2025-02-17) - - -### Bug Fixes - -* :bug: fixing deleted workspaces being cached ([624b206](https://github.com/kaneo-app/app/commit/624b20676fedf1b1a5871f916a5c1d5c38a5d2cb)) -* :bug: fixing overflowing workspace names ([0c2ab27](https://github.com/kaneo-app/app/commit/0c2ab2705d82c86ace8b4919c59d7773b63e989d)) -* :bug: fixing route generation for vite ([73ddd6d](https://github.com/kaneo-app/app/commit/73ddd6d0138e9223921cbefdd727b2fa61408be0)) -* :bug: remove unused import ([fa639e1](https://github.com/kaneo-app/app/commit/fa639e15e1c847d4ac89b7925fb2606b6e5900a4)) -* :construction_worker: fixing build on pipeline ([767ba10](https://github.com/kaneo-app/app/commit/767ba103aca3beeda0a6f0a0df4459ce071f3b74)) -* :green_heart: fixing formatting in package.json ([4644f0f](https://github.com/kaneo-app/app/commit/4644f0f6e3413591c9dd837a67df7cf8e735718e)) -* :sparkles: format drizzle.config.ts ([cccd3ae](https://github.com/kaneo-app/app/commit/cccd3aea9420d4815501ec0d15ef1dc08a1f1b15)) -* adding cursor button ([3b33c69](https://github.com/kaneo-app/app/commit/3b33c69f43c6adb6ad2e11284d20fb294941016b)) -* adding dynamic view height on sidebar ([e7f4360](https://github.com/kaneo-app/app/commit/e7f4360eaf74b57eb8de48c2709ac4bfd418ee60)) -* adding loading state for projects, sync ws when creating new tasks ([3977931](https://github.com/kaneo-app/app/commit/3977931ae07de1bcbf3ed1652ffead18b4469b2f)) -* changing release branch ([3636788](https://github.com/kaneo-app/app/commit/3636788ca23bf418ef098fa2dad4c0da67f09d74)) -* fixing build context ([fa736c5](https://github.com/kaneo-app/app/commit/fa736c5c60ac75b9a8d9a2f583c49b08d597d1cc)) -* fixing long task titles ([8927a97](https://github.com/kaneo-app/app/commit/8927a971e5b53cbc69bf543efd8bb1d7fd00778e)) -* fixing route selection and creating tasks with no asignee ([ae9bc8e](https://github.com/kaneo-app/app/commit/ae9bc8eebecc4786e3e7630432e9bc98cf03dd0f)) -* improving padding for user info section ([4e904bf](https://github.com/kaneo-app/app/commit/4e904bfc8051c49c3468690ae99fbce5706d7fcf)) -* improving scrolling on dnd ([17a54fd](https://github.com/kaneo-app/app/commit/17a54fdfcbcb9bc43efbe7dd05dd854872930ad9)) -* listing web's nginx conf ([9ae5b32](https://github.com/kaneo-app/app/commit/9ae5b3209d84529c7edc482f118826502327dfe9)) -* making settings not dependant on a workspaceId ([4a15188](https://github.com/kaneo-app/app/commit/4a15188bbdefa3cb8012f42f308bcd5cfae23882)) -* making sidebar fixed ([6761a4f](https://github.com/kaneo-app/app/commit/6761a4f9930d8f2c77241f2041c4b338750f7665)) -* making sidebar on mobile floating ([c3ba0e2](https://github.com/kaneo-app/app/commit/c3ba0e244a0d86a7390d97b145e0330b795d8410)) -* refactoring publishing flow ([5471b88](https://github.com/kaneo-app/app/commit/5471b88ee244064b69853fd0a914cf32803f9f8f)) -* removing unused import ([9466dc5](https://github.com/kaneo-app/app/commit/9466dc5d18e46d8f30000cf110ed255b53a3045d)) -* removing unused packages ([20a8c66](https://github.com/kaneo-app/app/commit/20a8c6694d1e1bc345655a4b85a56bf7a981dc48)) -* removing unused packages ([49ba8d1](https://github.com/kaneo-app/app/commit/49ba8d196d44715dd736ad3de4690a70686f46a3)) -* removing unused packages ([de87b29](https://github.com/kaneo-app/app/commit/de87b298d00cddf7fff0799bcce7149beb7f2123)) -* reseting zustand after sign out ([9352b9f](https://github.com/kaneo-app/app/commit/9352b9f77b10fbd37c1ad5557e3368ad27c1fdb1)) -* task title was overflowing when too long ([9627cb3](https://github.com/kaneo-app/app/commit/9627cb3c25b30e6fb5287a32aebb32bf76ddface)) -* updating docker context ([0bb17b5](https://github.com/kaneo-app/app/commit/0bb17b5d786c3ef110fbd16f08701b139bf39c7f)) -* updating reamde ([d6d3ed8](https://github.com/kaneo-app/app/commit/d6d3ed8bf8cab3b9a27747c66c4d0ffdf9e2ba13)) -* wrong z-index on modals ([2de17b8](https://github.com/kaneo-app/app/commit/2de17b8b1993d847a2f22a43891f2254b66ef3a2)) - - -### Features - -* :construction_worker: adding workflow to lint project ([382d6c5](https://github.com/kaneo-app/app/commit/382d6c5ef0a084d026a7238689f8a357fc05c5fa)) -* :construction_worker: updating husky and commitlint ([33e2920](https://github.com/kaneo-app/app/commit/33e292027fea1d6dc4546a61b869f180e7d129e0)) -* :construction_worker: updating workflow name ([f13d4ef](https://github.com/kaneo-app/app/commit/f13d4eff1c021be68b01c7381439d28629b6e22b)) -* :fire: adding initial kanban board ([0e2734a](https://github.com/kaneo-app/app/commit/0e2734a4757722d753be398c9f7273b7fdfc1274)) -* :fire: migrating to sessions, using file routes, adding auth provider ([d6f8ecc](https://github.com/kaneo-app/app/commit/d6f8ecce077e3fac67111e7585f81b6bd268d191)) -* :sparkles: adding crud for workspaces ([faad3a4](https://github.com/kaneo-app/app/commit/faad3a49a327ed3cbee14d96a923997a5daf8bbd)) -* :sparkles: adding marketing image ([841eee9](https://github.com/kaneo-app/app/commit/841eee9fcf4440370fdd95ee73731d591a6795b4)) -* :sparkles: adding marketing image ([91ac6c1](https://github.com/kaneo-app/app/commit/91ac6c189ddc81535cf498abcfb8e63a8c32cead)) -* :sparkles: adding marketing image ([e0dbd6b](https://github.com/kaneo-app/app/commit/e0dbd6bd41a440a0114e5c413d673601153d58a6)) -* :sparkles: adding marketing image ([a8568c1](https://github.com/kaneo-app/app/commit/a8568c1f6d04685d387996448830b1fb166740e5)) -* :sparkles: adding projects ([db2f600](https://github.com/kaneo-app/app/commit/db2f600d58ea45bf410f8b91de0577f969b2fbda)) -* :sparkles: finishing authentication, adding color modes ([da9c10f](https://github.com/kaneo-app/app/commit/da9c10fa56ccf479977d3fad8a547d684067256d)) -* :sparkles: finishing socket communication for tasks ([dcb8475](https://github.com/kaneo-app/app/commit/dcb84754b3bb970415bb7e16200224bef5271823)) -* :sparkles: initial commit for projects ([200d9a6](https://github.com/kaneo-app/app/commit/200d9a6df400bab61bbc63f2a28dc3807da77606)) -* :sparkles: updating logo design ([b8250e6](https://github.com/kaneo-app/app/commit/b8250e68fc3f8013b548750fb87140cb55811ac7)) -* adding docker images and compose ([537b47e](https://github.com/kaneo-app/app/commit/537b47e328b8b5ee2ef1f0ffb71e78e8e3a42ee8)) -* adding invites for users ([6f509ea](https://github.com/kaneo-app/app/commit/6f509ea85c76de40811282e673caa99ac174df69)) -* adding multi platform build ([9e40708](https://github.com/kaneo-app/app/commit/9e407089a09f93c0a5ecc8296444f0d3f61f3400)) -* adding pending invited users screen ([138dc70](https://github.com/kaneo-app/app/commit/138dc7084d9d30bc4a35e2ed94aed90e4c82dccc)) -* adding project icons ([500783e](https://github.com/kaneo-app/app/commit/500783eb12a2fc64ff7e64d078638a4d4a16a0f2)) -* adding project slugs ([e4cd25a](https://github.com/kaneo-app/app/commit/e4cd25a6b8c7bf6f6e3928b4dca6684270ed99a5)) -* adding sensors for dnd ([3a9f2a9](https://github.com/kaneo-app/app/commit/3a9f2a91eaf7cc8c5f330c69cbf702aed0ab0def)) -* adding settings page ([dd9ae8f](https://github.com/kaneo-app/app/commit/dd9ae8fd74008540c76caed0b7eed398ed408b54)) -* changing cover image ([62cb2fb](https://github.com/kaneo-app/app/commit/62cb2fb88e9e42de923d68bdd889f87738757905)) -* **create-turbo:** apply official-starter transform ([6fcda66](https://github.com/kaneo-app/app/commit/6fcda66be3d9e10f32705cd0a59d62eae0e8ef27)) -* **create-turbo:** apply package-manager transform ([2aaf064](https://github.com/kaneo-app/app/commit/2aaf064f095549ad6600e89954aba9fc2c8385d9)) -* **create-turbo:** create basic ([3b8654f](https://github.com/kaneo-app/app/commit/3b8654f88adfe575bdd6190af85ce8daeea7f810)) -* finishing responsive-ness on manage teams screens ([bf5c55c](https://github.com/kaneo-app/app/commit/bf5c55c073f073e6be6fbcb0e3f5cb31a5b0c893)) -* initial task edit setup ([2aacb26](https://github.com/kaneo-app/app/commit/2aacb262ab519eb1cc5e8c1a4aa3c1bcb9ba595c)) -* making manage teams screens responsive ([e78f23c](https://github.com/kaneo-app/app/commit/e78f23ce379ceeb3e980095932b300e7ef409755)) - - - # Changelog All notable changes to this project will be documented in this file.