From 1f83eb989cebdacd8dbd7c27350c3b277ec9619c Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Wed, 10 Apr 2024 16:11:38 +0545 Subject: [PATCH 1/8] feat(user): support list and getAddRolesToUserSql for new roles service --- packages/user/src/model/users/service.ts | 10 ++++++++ packages/user/src/model/users/sqlFactory.ts | 28 ++++++++++++++++++--- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/packages/user/src/model/users/service.ts b/packages/user/src/model/users/service.ts index 7b8189613..3a17faf30 100644 --- a/packages/user/src/model/users/service.ts +++ b/packages/user/src/model/users/service.ts @@ -18,6 +18,16 @@ class UserService< // eslint-disable-next-line prettier/prettier implements Service { + addRolesToUser = async (id: string | number, roleIds: number[]) => { + const query = this.factory.getAddRolesToUserSql(id, roleIds); + + const result = await this.database.connect((connection) => { + return connection.any(query); + }); + + return result; + }; + get table() { return this.config.user?.table?.name || TABLE_USERS; } diff --git a/packages/user/src/model/users/sqlFactory.ts b/packages/user/src/model/users/sqlFactory.ts index 31ffd273c..f36a8cfca 100644 --- a/packages/user/src/model/users/sqlFactory.ts +++ b/packages/user/src/model/users/sqlFactory.ts @@ -8,6 +8,7 @@ import humps from "humps"; import { QueryResultRow, QuerySqlToken, sql } from "slonik"; import { createSortFragment, createSortRoleFragment } from "./sql"; +import { TABLE_USER_ROLES } from "../../constants"; import type { SqlFactory, @@ -25,6 +26,26 @@ class UserSqlFactory< implements SqlFactory { /* eslint-enabled */ + getAddRolesToUserSql = ( + id: number | string, + roleIds: number[] + ): QuerySqlToken => { + const userRolesTableIdentifier = createTableIdentifier( + TABLE_USER_ROLES, + this.schema + ); + + return sql.unsafe` + INSERT INTO ${userRolesTableIdentifier} ("user_id", "role_id") + SELECT * + FROM ${sql.unnest( + roleIds.map((roleId) => { + return [id, roleId]; + }), + ["varchar", "int4"] + )} ON CONFLICT DO NOTHING; + `; + }; getFindByIdSql = (id: number | string): QuerySqlToken => { return sql.type(this.validationSchema)` @@ -57,11 +78,12 @@ class UserSqlFactory< COALESCE(user_role.role, '[]') AS roles FROM ${this.getTableFragment()} LEFT JOIN LATERAL ( - SELECT jsonb_agg(ur.role ${createSortRoleFragment( - sql.identifier(["ur", "role"]), + SELECT jsonb_agg(r ${createSortRoleFragment( + sql.identifier(["r", "id"]), sort )}) AS role - FROM "public"."st__user_roles" as ur + FROM "public"."user_roles" as ur + JOIN roles r ON ur.role_id = r.id WHERE ur.user_id = users.id ) AS user_role ON TRUE ${createFilterFragment(filters, tableIdentifier)} From 456f1c86fbb612394a94803596c3490a1e03b3f3 Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Wed, 10 Apr 2024 16:36:36 +0545 Subject: [PATCH 2/8] feat(user): support new roles by user update and findById --- packages/user/src/model/users/sqlFactory.ts | 29 ++++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/packages/user/src/model/users/sqlFactory.ts b/packages/user/src/model/users/sqlFactory.ts index f36a8cfca..724e4b57f 100644 --- a/packages/user/src/model/users/sqlFactory.ts +++ b/packages/user/src/model/users/sqlFactory.ts @@ -43,7 +43,20 @@ class UserSqlFactory< return [id, roleId]; }), ["varchar", "int4"] - )} ON CONFLICT DO NOTHING; + )} ON CONFLICT DO NOTHING + return + ${this.getTableFragment()}.*, + COALESCE(user_role.role, '[]') AS roles + FROM ${this.getTableFragment()} + LEFT JOIN LATERAL ( + SELECT jsonb_agg(r ${createSortRoleFragment( + sql.identifier(["r", "id"]) + )}) AS role + FROM "public"."user_roles" as ur + JOIN roles r ON ur.role_id = r.id + WHERE ur.user_id = users.id + ) AS user_role ON TRUE + WHERE id = ${id}; `; }; @@ -54,10 +67,11 @@ class UserSqlFactory< COALESCE(user_role.role, '[]') AS roles FROM ${this.getTableFragment()} LEFT JOIN LATERAL ( - SELECT jsonb_agg(ur.role ${createSortRoleFragment( - sql.identifier(["ur", "role"]) + SELECT jsonb_agg(r ${createSortRoleFragment( + sql.identifier(["r", "id"]) )}) AS role - FROM "public"."st__user_roles" as ur + FROM "public"."user_roles" as ur + JOIN roles r ON ur.role_id = r.id WHERE ur.user_id = users.id ) AS user_role ON TRUE WHERE id = ${id}; @@ -113,10 +127,11 @@ class UserSqlFactory< SELECT COALESCE(user_role.role, '[]') AS roles FROM ${this.getTableFragment()} LEFT JOIN LATERAL ( - SELECT jsonb_agg(ur.role ${createSortRoleFragment( - sql.identifier(["ur", "role"]) + SELECT jsonb_agg(r ${createSortRoleFragment( + sql.identifier(["r", "id"]) )}) AS role - FROM "public"."st__user_roles" as ur + FROM "public"."user_roles" as ur + JOIN roles r ON ur.role_id = r.id WHERE ur.user_id = users.id ) AS user_role ON TRUE WHERE id = ${id} From c62d372e9c7d1fa8e0ff26b20606ee27f8aec6cb Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Wed, 10 Apr 2024 17:13:29 +0545 Subject: [PATCH 3/8] feat(user): update user type --- .../recipes/third-party-email-password/emailPasswordSignUp.ts | 4 ++-- .../config/third-party-email-password/emailPasswordSignUp.ts | 4 ++-- packages/user/src/types/index.ts | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/emailPasswordSignUp.ts b/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/emailPasswordSignUp.ts index 28830e311..83e80de50 100644 --- a/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/emailPasswordSignUp.ts +++ b/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/emailPasswordSignUp.ts @@ -48,6 +48,8 @@ const emailPasswordSignUp = ( input.userContext.tenant ); + // TODO: assign roles to user + let user: User | null | undefined; try { @@ -73,8 +75,6 @@ const emailPasswordSignUp = ( }; } - user.roles = roles; - originalResponse.user = { ...originalResponse.user, ...user, diff --git a/packages/user/src/supertokens/recipes/config/third-party-email-password/emailPasswordSignUp.ts b/packages/user/src/supertokens/recipes/config/third-party-email-password/emailPasswordSignUp.ts index 9bbb3a788..35a2acd1a 100644 --- a/packages/user/src/supertokens/recipes/config/third-party-email-password/emailPasswordSignUp.ts +++ b/packages/user/src/supertokens/recipes/config/third-party-email-password/emailPasswordSignUp.ts @@ -45,6 +45,8 @@ const emailPasswordSignUp = ( email: originalResponse.user.email, }); + // TODO: assign roles to user + if (!user) { throw new Error("User not found"); } @@ -62,8 +64,6 @@ const emailPasswordSignUp = ( }; } - user.roles = roles; - originalResponse.user = { ...originalResponse.user, ...user, diff --git a/packages/user/src/types/index.ts b/packages/user/src/types/index.ts index 8103cc06a..a9fdc3915 100644 --- a/packages/user/src/types/index.ts +++ b/packages/user/src/types/index.ts @@ -1,3 +1,4 @@ +import type { Role } from "./roles"; import type { PaginatedList } from "@dzangolab/fastify-slonik"; import type { MercuriusContext } from "mercurius"; import type { QueryResultRow } from "slonik"; @@ -33,7 +34,7 @@ interface User { disabled: boolean; email: string; lastLoginAt: number; - roles?: string[]; + roles: Omit[]; signedUpAt: number; } From b21c26eeb8c9d50478ca160f1b62a1180a4312a1 Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Thu, 11 Apr 2024 14:34:24 +0545 Subject: [PATCH 4/8] chore: remove todo --- .../emailPasswordSignUp.ts | 2 - packages/user/src/model/users/service.ts | 48 +++++++++---------- .../emailPasswordSignUp.ts | 2 - 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/emailPasswordSignUp.ts b/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/emailPasswordSignUp.ts index 83e80de50..9d3c11e0f 100644 --- a/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/emailPasswordSignUp.ts +++ b/packages/multi-tenant/src/supertokens/recipes/third-party-email-password/emailPasswordSignUp.ts @@ -48,8 +48,6 @@ const emailPasswordSignUp = ( input.userContext.tenant ); - // TODO: assign roles to user - let user: User | null | undefined; try { diff --git a/packages/user/src/model/users/service.ts b/packages/user/src/model/users/service.ts index 3a17faf30..b2c6493c9 100644 --- a/packages/user/src/model/users/service.ts +++ b/packages/user/src/model/users/service.ts @@ -28,30 +28,6 @@ class UserService< return result; }; - get table() { - return this.config.user?.table?.name || TABLE_USERS; - } - - get factory() { - if (!this.table) { - throw new Error(`Service table is not defined`); - } - - if (!this._factory) { - this._factory = new UserSqlFactory< - User, - UserCreateInput, - UserUpdateInput - >(this); - } - - return this._factory as UserSqlFactory< - User, - UserCreateInput, - UserUpdateInput - >; - } - changePassword = async ( userId: string, oldPassword: string, @@ -114,6 +90,30 @@ class UserService< }; } }; + + get table() { + return this.config.user?.table?.name || TABLE_USERS; + } + + get factory() { + if (!this.table) { + throw new Error(`Service table is not defined`); + } + + if (!this._factory) { + this._factory = new UserSqlFactory< + User, + UserCreateInput, + UserUpdateInput + >(this); + } + + return this._factory as UserSqlFactory< + User, + UserCreateInput, + UserUpdateInput + >; + } } export default UserService; diff --git a/packages/user/src/supertokens/recipes/config/third-party-email-password/emailPasswordSignUp.ts b/packages/user/src/supertokens/recipes/config/third-party-email-password/emailPasswordSignUp.ts index 35a2acd1a..dac59697f 100644 --- a/packages/user/src/supertokens/recipes/config/third-party-email-password/emailPasswordSignUp.ts +++ b/packages/user/src/supertokens/recipes/config/third-party-email-password/emailPasswordSignUp.ts @@ -45,8 +45,6 @@ const emailPasswordSignUp = ( email: originalResponse.user.email, }); - // TODO: assign roles to user - if (!user) { throw new Error("User not found"); } From ac9d7f4dc405f70da25da1e5b4a5051daffcf974 Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Mon, 15 Apr 2024 13:38:40 +0545 Subject: [PATCH 5/8] feat(user): add isAdminExists method for user service --- packages/user/src/model/users/service.ts | 12 ++++++++ packages/user/src/model/users/sqlFactory.ts | 34 +++++++++++++++++++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/packages/user/src/model/users/service.ts b/packages/user/src/model/users/service.ts index b2c6493c9..d2d2fb441 100644 --- a/packages/user/src/model/users/service.ts +++ b/packages/user/src/model/users/service.ts @@ -91,6 +91,18 @@ class UserService< } }; + isAdminExists = async (): Promise => { + const query = this.factory.getIsAdminExistsSql(); + + const result = await this.database.connect(async (connection) => { + const columns = await connection.one(query); + + return columns.isAdminExists; + }); + + return result as boolean; + }; + get table() { return this.config.user?.table?.name || TABLE_USERS; } diff --git a/packages/user/src/model/users/sqlFactory.ts b/packages/user/src/model/users/sqlFactory.ts index 724e4b57f..29dcb48c0 100644 --- a/packages/user/src/model/users/sqlFactory.ts +++ b/packages/user/src/model/users/sqlFactory.ts @@ -5,16 +5,23 @@ import { createTableIdentifier, } from "@dzangolab/fastify-slonik"; import humps from "humps"; -import { QueryResultRow, QuerySqlToken, sql } from "slonik"; +import { sql } from "slonik"; +import { z } from "zod"; import { createSortFragment, createSortRoleFragment } from "./sql"; -import { TABLE_USER_ROLES } from "../../constants"; +import { + ROLE_ADMIN, + ROLE_SUPERADMIN, + TABLE_ROLES, + TABLE_USER_ROLES, +} from "../../constants"; import type { SqlFactory, FilterInput, SortInput, } from "@dzangolab/fastify-slonik"; +import type { QueryResultRow, QuerySqlToken } from "slonik"; /* eslint-disable brace-style */ class UserSqlFactory< @@ -78,6 +85,29 @@ class UserSqlFactory< `; }; + getIsAdminExistsSql = () => { + const rolesTableIdentifier = createTableIdentifier(TABLE_ROLES); + + const userRolesTableIdentifier = createTableIdentifier(TABLE_USER_ROLES); + + const schema = z.object({ + isAdminExists: z.boolean(), + }); + + return sql.type(schema)` + SELECT EXISTS ( + SELECT 1 + FROM ${this.getTableFragment()} u + INNER JOIN ${userRolesTableIdentifier} ur ON u.id = ur.user_id + INNER JOIN ${rolesTableIdentifier} r ON ur.role_id = r.id + WHERE r.role IN ${sql.join( + [ROLE_ADMIN, ROLE_SUPERADMIN], + sql.fragment` AND ` + )} + ); + `; + }; + getListSql = ( limit: number, offset?: number, From 79f9364304a62130c270a16c5ccae8f721e5f94d Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Mon, 15 Apr 2024 14:32:06 +0545 Subject: [PATCH 6/8] chore(fix): fix getIsAdminExitsSql --- packages/user/src/model/users/sqlFactory.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/user/src/model/users/sqlFactory.ts b/packages/user/src/model/users/sqlFactory.ts index 29dcb48c0..646e274de 100644 --- a/packages/user/src/model/users/sqlFactory.ts +++ b/packages/user/src/model/users/sqlFactory.ts @@ -100,11 +100,11 @@ class UserSqlFactory< FROM ${this.getTableFragment()} u INNER JOIN ${userRolesTableIdentifier} ur ON u.id = ur.user_id INNER JOIN ${rolesTableIdentifier} r ON ur.role_id = r.id - WHERE r.role IN ${sql.join( + WHERE r.role IN (${sql.join( [ROLE_ADMIN, ROLE_SUPERADMIN], - sql.fragment` AND ` - )} - ); + sql.fragment`, ` + )}) + ) as is_admin_exists; `; }; From bcab1fc5dd1241046b4c8bf7fc96275b1e105655 Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Mon, 15 Apr 2024 15:10:28 +0545 Subject: [PATCH 7/8] chore: improve style --- packages/user/src/model/users/sqlFactory.ts | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/packages/user/src/model/users/sqlFactory.ts b/packages/user/src/model/users/sqlFactory.ts index 646e274de..fd4f608aa 100644 --- a/packages/user/src/model/users/sqlFactory.ts +++ b/packages/user/src/model/users/sqlFactory.ts @@ -37,10 +37,7 @@ class UserSqlFactory< id: number | string, roleIds: number[] ): QuerySqlToken => { - const userRolesTableIdentifier = createTableIdentifier( - TABLE_USER_ROLES, - this.schema - ); + const userRolesTableIdentifier = createTableIdentifier(TABLE_USER_ROLES); return sql.unsafe` INSERT INTO ${userRolesTableIdentifier} ("user_id", "role_id") @@ -50,20 +47,7 @@ class UserSqlFactory< return [id, roleId]; }), ["varchar", "int4"] - )} ON CONFLICT DO NOTHING - return - ${this.getTableFragment()}.*, - COALESCE(user_role.role, '[]') AS roles - FROM ${this.getTableFragment()} - LEFT JOIN LATERAL ( - SELECT jsonb_agg(r ${createSortRoleFragment( - sql.identifier(["r", "id"]) - )}) AS role - FROM "public"."user_roles" as ur - JOIN roles r ON ur.role_id = r.id - WHERE ur.user_id = users.id - ) AS user_role ON TRUE - WHERE id = ${id}; + )} ON CONFLICT DO NOTHING; `; }; From 818758a4cec0f638ea9db97db8712067ed2ba341 Mon Sep 17 00:00:00 2001 From: Dipendra Upreti Date: Mon, 22 Apr 2024 16:27:08 +0545 Subject: [PATCH 8/8] refactor(user): remove public as default scheam for user service --- packages/user/src/model/users/sqlFactory.ts | 27 ++++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/packages/user/src/model/users/sqlFactory.ts b/packages/user/src/model/users/sqlFactory.ts index fd4f608aa..e047741db 100644 --- a/packages/user/src/model/users/sqlFactory.ts +++ b/packages/user/src/model/users/sqlFactory.ts @@ -3,6 +3,7 @@ import { createLimitFragment, createFilterFragment, createTableIdentifier, + createTableFragment, } from "@dzangolab/fastify-slonik"; import humps from "humps"; import { sql } from "slonik"; @@ -37,10 +38,8 @@ class UserSqlFactory< id: number | string, roleIds: number[] ): QuerySqlToken => { - const userRolesTableIdentifier = createTableIdentifier(TABLE_USER_ROLES); - return sql.unsafe` - INSERT INTO ${userRolesTableIdentifier} ("user_id", "role_id") + INSERT INTO ${this.userRolesIdentifier} ("user_id", "role_id") SELECT * FROM ${sql.unnest( roleIds.map((roleId) => { @@ -61,7 +60,7 @@ class UserSqlFactory< SELECT jsonb_agg(r ${createSortRoleFragment( sql.identifier(["r", "id"]) )}) AS role - FROM "public"."user_roles" as ur + FROM ${this.userRolesIdentifier} as ur JOIN roles r ON ur.role_id = r.id WHERE ur.user_id = users.id ) AS user_role ON TRUE @@ -70,10 +69,6 @@ class UserSqlFactory< }; getIsAdminExistsSql = () => { - const rolesTableIdentifier = createTableIdentifier(TABLE_ROLES); - - const userRolesTableIdentifier = createTableIdentifier(TABLE_USER_ROLES); - const schema = z.object({ isAdminExists: z.boolean(), }); @@ -82,8 +77,8 @@ class UserSqlFactory< SELECT EXISTS ( SELECT 1 FROM ${this.getTableFragment()} u - INNER JOIN ${userRolesTableIdentifier} ur ON u.id = ur.user_id - INNER JOIN ${rolesTableIdentifier} r ON ur.role_id = r.id + INNER JOIN ${this.userRolesIdentifier} ur ON u.id = ur.user_id + INNER JOIN ${this.rolesIdentifier} r ON ur.role_id = r.id WHERE r.role IN (${sql.join( [ROLE_ADMIN, ROLE_SUPERADMIN], sql.fragment`, ` @@ -110,7 +105,7 @@ class UserSqlFactory< sql.identifier(["r", "id"]), sort )}) AS role - FROM "public"."user_roles" as ur + FROM ${this.userRolesIdentifier} as ur JOIN roles r ON ur.role_id = r.id WHERE ur.user_id = users.id ) AS user_role ON TRUE @@ -144,7 +139,7 @@ class UserSqlFactory< SELECT jsonb_agg(r ${createSortRoleFragment( sql.identifier(["r", "id"]) )}) AS role - FROM "public"."user_roles" as ur + FROM ${this.userRolesIdentifier} as ur JOIN roles r ON ur.role_id = r.id WHERE ur.user_id = users.id ) AS user_role ON TRUE @@ -152,6 +147,14 @@ class UserSqlFactory< ) as roles; `; }; + + get rolesIdentifier() { + return createTableFragment(TABLE_ROLES); + } + + get userRolesIdentifier() { + return createTableIdentifier(TABLE_USER_ROLES); + } } export default UserSqlFactory;