2022-10-21 00:14:17 -05:00
|
|
|
import type { User, CreateUser } from '@logto/schemas';
|
|
|
|
import { Users, UserRole } from '@logto/schemas';
|
|
|
|
import type { OmitAutoSetFields } from '@logto/shared';
|
|
|
|
import { conditionalSql, convertToIdentifiers } from '@logto/shared';
|
2021-07-02 08:14:18 -05:00
|
|
|
import { sql } from 'slonik';
|
2021-08-29 22:30:54 -05:00
|
|
|
|
2021-11-17 22:26:34 -05:00
|
|
|
import { buildUpdateWhere } from '@/database/update-where';
|
2022-04-19 08:49:20 -05:00
|
|
|
import envSet from '@/env-set';
|
2022-06-17 01:45:39 -05:00
|
|
|
import { DeletionError } from '@/errors/SlonikError';
|
2021-07-02 08:14:18 -05:00
|
|
|
|
|
|
|
const { table, fields } = convertToIdentifiers(Users);
|
|
|
|
|
2021-07-26 09:32:18 -05:00
|
|
|
export const findUserByUsername = async (username: string) =>
|
2022-06-02 03:29:17 -05:00
|
|
|
envSet.pool.maybeOne<User>(sql`
|
2021-12-20 01:20:23 -05:00
|
|
|
select ${sql.join(Object.values(fields), sql`,`)}
|
|
|
|
from ${table}
|
|
|
|
where ${fields.username}=${username}
|
|
|
|
`);
|
2021-07-26 09:32:18 -05:00
|
|
|
|
2022-01-30 22:04:55 -05:00
|
|
|
export const findUserByEmail = async (email: string) =>
|
2022-11-11 01:54:46 -05:00
|
|
|
envSet.pool.maybeOne<User>(sql`
|
2022-01-30 22:04:55 -05:00
|
|
|
select ${sql.join(Object.values(fields), sql`,`)}
|
|
|
|
from ${table}
|
2022-11-09 02:33:07 -05:00
|
|
|
where lower(${fields.primaryEmail})=lower(${email})
|
2022-01-30 22:04:55 -05:00
|
|
|
`);
|
|
|
|
|
2022-02-09 03:14:42 -05:00
|
|
|
export const findUserByPhone = async (phone: string) =>
|
2022-11-11 01:54:46 -05:00
|
|
|
envSet.pool.maybeOne<User>(sql`
|
2022-02-09 03:14:42 -05:00
|
|
|
select ${sql.join(Object.values(fields), sql`,`)}
|
|
|
|
from ${table}
|
|
|
|
where ${fields.primaryPhone}=${phone}
|
|
|
|
`);
|
|
|
|
|
2021-07-02 08:14:18 -05:00
|
|
|
export const findUserById = async (id: string) =>
|
2022-04-19 08:49:20 -05:00
|
|
|
envSet.pool.one<User>(sql`
|
2021-12-20 01:20:23 -05:00
|
|
|
select ${sql.join(Object.values(fields), sql`,`)}
|
|
|
|
from ${table}
|
|
|
|
where ${fields.id}=${id}
|
|
|
|
`);
|
2021-07-04 04:41:46 -05:00
|
|
|
|
2022-06-16 22:00:02 -05:00
|
|
|
export const findUserByIdentity = async (target: string, userId: string) =>
|
2022-04-19 08:49:20 -05:00
|
|
|
envSet.pool.one<User>(
|
2022-02-11 02:19:18 -05:00
|
|
|
sql`
|
|
|
|
select ${sql.join(Object.values(fields), sql`,`)}
|
|
|
|
from ${table}
|
2022-06-16 22:00:02 -05:00
|
|
|
where ${fields.identities}::json#>>'{${sql.identifier([target])},userId}' = ${userId}
|
2022-02-11 02:19:18 -05:00
|
|
|
`
|
|
|
|
);
|
|
|
|
|
2022-11-10 06:27:53 -05:00
|
|
|
export const hasUser = async (username: string, excludeUserId?: string) =>
|
2022-04-19 08:49:20 -05:00
|
|
|
envSet.pool.exists(sql`
|
2021-12-20 01:20:23 -05:00
|
|
|
select ${fields.id}
|
|
|
|
from ${table}
|
|
|
|
where ${fields.username}=${username}
|
2022-11-10 06:27:53 -05:00
|
|
|
${conditionalSql(excludeUserId, (id) => sql`and ${fields.id}<>${id}`)}
|
2021-12-20 01:20:23 -05:00
|
|
|
`);
|
2021-07-04 04:41:46 -05:00
|
|
|
|
|
|
|
export const hasUserWithId = async (id: string) =>
|
2022-04-19 08:49:20 -05:00
|
|
|
envSet.pool.exists(sql`
|
2021-12-20 01:20:23 -05:00
|
|
|
select ${fields.id}
|
|
|
|
from ${table}
|
|
|
|
where ${fields.id}=${id}
|
|
|
|
`);
|
2021-07-04 04:41:46 -05:00
|
|
|
|
2022-11-10 06:27:53 -05:00
|
|
|
export const hasUserWithEmail = async (email: string, excludeUserId?: string) =>
|
2022-04-19 08:49:20 -05:00
|
|
|
envSet.pool.exists(sql`
|
2022-02-07 01:14:42 -05:00
|
|
|
select ${fields.primaryEmail}
|
|
|
|
from ${table}
|
2022-11-09 02:33:07 -05:00
|
|
|
where lower(${fields.primaryEmail})=lower(${email})
|
2022-11-10 06:27:53 -05:00
|
|
|
${conditionalSql(excludeUserId, (id) => sql`and ${fields.id}<>${id}`)}
|
2022-02-07 01:14:42 -05:00
|
|
|
`);
|
|
|
|
|
2022-11-10 06:27:53 -05:00
|
|
|
export const hasUserWithPhone = async (phone: string, excludeUserId?: string) =>
|
2022-04-19 08:49:20 -05:00
|
|
|
envSet.pool.exists(sql`
|
2022-02-09 03:14:42 -05:00
|
|
|
select ${fields.primaryPhone}
|
|
|
|
from ${table}
|
|
|
|
where ${fields.primaryPhone}=${phone}
|
2022-11-10 06:27:53 -05:00
|
|
|
${conditionalSql(excludeUserId, (id) => sql`and ${fields.id}<>${id}`)}
|
2022-02-09 03:14:42 -05:00
|
|
|
`);
|
|
|
|
|
2022-06-16 22:00:02 -05:00
|
|
|
export const hasUserWithIdentity = async (target: string, userId: string) =>
|
2022-04-19 08:49:20 -05:00
|
|
|
envSet.pool.exists(
|
2022-02-11 02:19:18 -05:00
|
|
|
sql`
|
|
|
|
select ${fields.id}
|
|
|
|
from ${table}
|
2022-06-16 22:00:02 -05:00
|
|
|
where ${fields.identities}::json#>>'{${sql.identifier([target])},userId}' = ${userId}
|
2022-02-11 02:19:18 -05:00
|
|
|
`
|
|
|
|
);
|
|
|
|
|
2022-11-03 21:47:54 -05:00
|
|
|
const buildUserSearchConditionSql = (search: string, isCaseSensitive = false) => {
|
2022-02-23 23:29:34 -05:00
|
|
|
const searchFields = [fields.primaryEmail, fields.primaryPhone, fields.username, fields.name];
|
2022-02-16 02:55:08 -05:00
|
|
|
|
2022-11-03 21:47:54 -05:00
|
|
|
return sql`${sql.join(
|
|
|
|
searchFields.map(
|
|
|
|
(filedName) =>
|
|
|
|
sql`${filedName} ${isCaseSensitive ? sql`like` : sql`ilike`} ${'%' + search + '%'}`
|
|
|
|
),
|
|
|
|
sql` or `
|
|
|
|
)}`;
|
2022-02-23 23:29:34 -05:00
|
|
|
};
|
2022-02-16 02:55:08 -05:00
|
|
|
|
2022-11-03 21:47:54 -05:00
|
|
|
const buildUserConditions = (
|
|
|
|
search?: string,
|
|
|
|
hideAdminUser?: boolean,
|
|
|
|
isCaseSensitive?: boolean
|
|
|
|
) => {
|
2022-06-21 21:22:15 -05:00
|
|
|
if (hideAdminUser) {
|
|
|
|
return sql`
|
|
|
|
where not (${fields.roleNames}::jsonb?${UserRole.Admin})
|
2022-11-03 21:47:54 -05:00
|
|
|
${conditionalSql(
|
|
|
|
search,
|
|
|
|
(search) => sql`and (${buildUserSearchConditionSql(search, isCaseSensitive)})`
|
|
|
|
)}
|
2022-06-21 21:22:15 -05:00
|
|
|
`;
|
|
|
|
}
|
|
|
|
|
|
|
|
return sql`
|
2022-11-03 21:47:54 -05:00
|
|
|
${conditionalSql(
|
|
|
|
search,
|
|
|
|
(search) => sql`where ${buildUserSearchConditionSql(search, isCaseSensitive)}`
|
|
|
|
)}
|
2022-06-21 21:22:15 -05:00
|
|
|
`;
|
|
|
|
};
|
|
|
|
|
2022-11-03 21:47:54 -05:00
|
|
|
export const countUsers = async (
|
|
|
|
search?: string,
|
|
|
|
hideAdminUser?: boolean,
|
|
|
|
isCaseSensitive?: boolean
|
|
|
|
) =>
|
2022-04-19 08:49:20 -05:00
|
|
|
envSet.pool.one<{ count: number }>(sql`
|
2022-02-23 23:29:34 -05:00
|
|
|
select count(*)
|
|
|
|
from ${table}
|
2022-11-03 21:47:54 -05:00
|
|
|
${buildUserConditions(search, hideAdminUser, isCaseSensitive)}
|
2022-02-23 23:29:34 -05:00
|
|
|
`);
|
|
|
|
|
2022-06-21 21:22:15 -05:00
|
|
|
export const findUsers = async (
|
|
|
|
limit: number,
|
|
|
|
offset: number,
|
|
|
|
search?: string,
|
2022-11-03 21:47:54 -05:00
|
|
|
hideAdminUser?: boolean,
|
|
|
|
isCaseSensitive?: boolean
|
2022-06-21 21:22:15 -05:00
|
|
|
) =>
|
2022-04-19 08:49:20 -05:00
|
|
|
envSet.pool.any<User>(
|
2022-02-23 23:29:34 -05:00
|
|
|
sql`
|
|
|
|
select ${sql.join(Object.values(fields), sql`,`)}
|
|
|
|
from ${table}
|
2022-11-03 21:47:54 -05:00
|
|
|
${buildUserConditions(search, hideAdminUser, isCaseSensitive)}
|
2022-02-23 23:29:34 -05:00
|
|
|
limit ${limit}
|
|
|
|
offset ${offset}
|
|
|
|
`
|
|
|
|
);
|
2021-11-17 22:26:34 -05:00
|
|
|
|
2022-04-19 08:49:20 -05:00
|
|
|
const updateUser = buildUpdateWhere<CreateUser, User>(Users, true);
|
2021-11-17 22:26:34 -05:00
|
|
|
|
2022-06-14 08:38:10 -05:00
|
|
|
export const updateUserById = async (
|
|
|
|
id: string,
|
|
|
|
set: Partial<OmitAutoSetFields<CreateUser>>,
|
|
|
|
jsonbMode: 'replace' | 'merge' = 'merge'
|
|
|
|
) => updateUser({ set, where: { id }, jsonbMode });
|
2021-11-17 22:26:34 -05:00
|
|
|
|
|
|
|
export const deleteUserById = async (id: string) => {
|
2022-04-19 08:49:20 -05:00
|
|
|
const { rowCount } = await envSet.pool.query(sql`
|
2021-12-20 01:20:23 -05:00
|
|
|
delete from ${table}
|
2022-02-28 01:30:27 -05:00
|
|
|
where ${fields.id}=${id}
|
2021-12-20 01:20:23 -05:00
|
|
|
`);
|
2022-01-27 06:26:34 -05:00
|
|
|
|
2021-11-17 22:26:34 -05:00
|
|
|
if (rowCount < 1) {
|
2022-02-13 22:50:47 -05:00
|
|
|
throw new DeletionError(Users.table, id);
|
2021-11-17 22:26:34 -05:00
|
|
|
}
|
|
|
|
};
|
2022-02-17 01:10:26 -05:00
|
|
|
|
2022-06-16 22:00:02 -05:00
|
|
|
export const deleteUserIdentity = async (userId: string, target: string) =>
|
2022-04-19 08:49:20 -05:00
|
|
|
envSet.pool.one<User>(sql`
|
2022-03-25 02:48:53 -05:00
|
|
|
update ${table}
|
2022-06-16 22:00:02 -05:00
|
|
|
set ${fields.identities}=${fields.identities}::jsonb-${target}
|
2022-03-25 02:48:53 -05:00
|
|
|
where ${fields.id}=${userId}
|
|
|
|
returning *
|
|
|
|
`);
|
2022-06-08 22:42:52 -05:00
|
|
|
|
|
|
|
export const hasActiveUsers = async () =>
|
|
|
|
envSet.pool.exists(sql`
|
|
|
|
select ${fields.id}
|
|
|
|
from ${table}
|
|
|
|
limit 1
|
|
|
|
`);
|
2022-09-29 02:32:43 -05:00
|
|
|
|
|
|
|
export const getDailyNewUserCountsByTimeInterval = async (
|
|
|
|
startTimeExclusive: number,
|
|
|
|
endTimeInclusive: number
|
|
|
|
) =>
|
|
|
|
envSet.pool.any<{ date: string; count: number }>(sql`
|
|
|
|
select date(${fields.createdAt}), count(*)
|
|
|
|
from ${table}
|
|
|
|
where ${fields.createdAt} > to_timestamp(${startTimeExclusive}::double precision / 1000)
|
|
|
|
and ${fields.createdAt} <= to_timestamp(${endTimeInclusive}::double precision / 1000)
|
|
|
|
group by date(${fields.createdAt})
|
|
|
|
`);
|