Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,6 @@ const emailPasswordSignUp = (
};
}

user.roles = roles;

originalResponse.user = {
...originalResponse.user,
...user,
Expand Down
64 changes: 43 additions & 21 deletions packages/user/src/model/users/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,15 @@ class UserService<
// eslint-disable-next-line prettier/prettier
implements Service<User, UserCreateInput, UserUpdateInput> {

get table() {
return this.config.user?.table?.name || TABLE_USERS;
}
addRolesToUser = async (id: string | number, roleIds: number[]) => {
const query = this.factory.getAddRolesToUserSql(id, roleIds);

get factory() {
if (!this.table) {
throw new Error(`Service table is not defined`);
}

if (!this._factory) {
this._factory = new UserSqlFactory<
User,
UserCreateInput,
UserUpdateInput
>(this);
}
const result = await this.database.connect((connection) => {
return connection.any(query);
});

return this._factory as UserSqlFactory<
User,
UserCreateInput,
UserUpdateInput
>;
}
return result;
};

changePassword = async (
userId: string,
Expand Down Expand Up @@ -104,6 +90,42 @@ class UserService<
};
}
};

isAdminExists = async (): Promise<boolean> => {
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;
}

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;
74 changes: 64 additions & 10 deletions packages/user/src/model/users/sqlFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,26 @@ import {
createLimitFragment,
createFilterFragment,
createTableIdentifier,
createTableFragment,
} 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 {
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<
Expand All @@ -25,6 +34,21 @@ class UserSqlFactory<
implements SqlFactory<User, UserCreateInput, UserUpdateInput>
{
/* eslint-enabled */
getAddRolesToUserSql = (
id: number | string,
roleIds: number[]
): QuerySqlToken => {
return sql.unsafe`
INSERT INTO ${this.userRolesIdentifier} ("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)`
Expand All @@ -33,16 +57,36 @@ 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 ${this.userRolesIdentifier} 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};
`;
};

getIsAdminExistsSql = () => {
const schema = z.object({
isAdminExists: z.boolean(),
});

return sql.type(schema)`
SELECT EXISTS (
SELECT 1
FROM ${this.getTableFragment()} u
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`, `
)})
) as is_admin_exists;
`;
};

getListSql = (
limit: number,
offset?: number,
Expand All @@ -57,11 +101,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 ${this.userRolesIdentifier} 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)}
Expand Down Expand Up @@ -91,16 +136,25 @@ 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 ${this.userRolesIdentifier} 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}
) as roles;
`;
};

get rolesIdentifier() {
return createTableFragment(TABLE_ROLES);
}

get userRolesIdentifier() {
return createTableIdentifier(TABLE_USER_ROLES);
}
}

export default UserSqlFactory;
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,6 @@ const emailPasswordSignUp = (
};
}

user.roles = roles;

originalResponse.user = {
...originalResponse.user,
...user,
Expand Down
3 changes: 2 additions & 1 deletion packages/user/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -33,7 +34,7 @@ interface User {
disabled: boolean;
email: string;
lastLoginAt: number;
roles?: string[];
roles: Omit<Role, "permissions">[];
signedUpAt: number;
}

Expand Down