mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
refactor(core): count new users by created_at (#2027)
This commit is contained in:
parent
11381afdc5
commit
cce2d40160
8 changed files with 51 additions and 30 deletions
|
@ -17,6 +17,7 @@ export const mockUser: User = {
|
|||
customData: {},
|
||||
applicationId: 'bar',
|
||||
lastSignInAt: 1_650_969_465_789,
|
||||
createdAt: 1_650_969_000_000,
|
||||
};
|
||||
|
||||
export const mockUserResponse = pick(mockUser, ...userInfoSelectFields);
|
||||
|
@ -38,6 +39,7 @@ export const mockUserWithPassword: User = {
|
|||
customData: {},
|
||||
applicationId: 'bar',
|
||||
lastSignInAt: 1_650_969_465_789,
|
||||
createdAt: 1_650_969_000_000,
|
||||
};
|
||||
|
||||
export const mockUserList: User[] = [
|
||||
|
@ -55,6 +57,7 @@ export const mockUserList: User[] = [
|
|||
customData: {},
|
||||
applicationId: 'bar',
|
||||
lastSignInAt: 1_650_969_465_000,
|
||||
createdAt: 1_650_969_000_000,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
|
@ -70,6 +73,7 @@ export const mockUserList: User[] = [
|
|||
customData: {},
|
||||
applicationId: 'bar',
|
||||
lastSignInAt: 1_650_969_465_000,
|
||||
createdAt: 1_650_969_000_000,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
|
@ -85,6 +89,7 @@ export const mockUserList: User[] = [
|
|||
customData: {},
|
||||
applicationId: 'bar',
|
||||
lastSignInAt: 1_650_969_465_000,
|
||||
createdAt: 1_650_969_000_000,
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
|
@ -100,6 +105,7 @@ export const mockUserList: User[] = [
|
|||
customData: {},
|
||||
applicationId: 'bar',
|
||||
lastSignInAt: 1_650_969_465_000,
|
||||
createdAt: 1_650_969_000_000,
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
|
@ -115,6 +121,7 @@ export const mockUserList: User[] = [
|
|||
customData: {},
|
||||
applicationId: 'bar',
|
||||
lastSignInAt: 1_650_969_465_000,
|
||||
createdAt: 1_650_969_000_000,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -49,27 +49,6 @@ export const findLogs = async (limit: number, offset: number, logCondition: LogC
|
|||
|
||||
export const findLogById = buildFindEntityById<CreateLog, Log>(Logs);
|
||||
|
||||
const registerLogTypes: LogType[] = [
|
||||
'RegisterUsernamePassword',
|
||||
'RegisterEmail',
|
||||
'RegisterSms',
|
||||
'RegisterSocial',
|
||||
];
|
||||
|
||||
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)
|
||||
and ${fields.type} in (${sql.join(registerLogTypes, sql`, `)})
|
||||
and ${fields.payload}->>'result' = 'Success'
|
||||
group by date(${fields.createdAt})
|
||||
`);
|
||||
|
||||
// The active user should exchange the tokens by the authorization code (i.e. sign-in)
|
||||
// or exchange the access token, which will expire in 2 hours, by the refresh token.
|
||||
const activeUserLogTypes: LogType[] = ['CodeExchangeToken', 'RefreshTokenExchangeToken'];
|
||||
|
|
|
@ -158,3 +158,15 @@ export const hasActiveUsers = async () =>
|
|||
from ${table}
|
||||
limit 1
|
||||
`);
|
||||
|
||||
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})
|
||||
`);
|
||||
|
|
|
@ -5,9 +5,16 @@ import { createRequester } from '@/utils/test-utils';
|
|||
|
||||
const totalUserCount = 1000;
|
||||
const countUsers = jest.fn(async () => ({ count: totalUserCount }));
|
||||
const getDailyNewUserCountsByTimeInterval = jest.fn(
|
||||
async (startTimeExclusive: number, endTimeInclusive: number) => mockDailyNewUserCounts
|
||||
);
|
||||
|
||||
jest.mock('@/queries/user', () => ({
|
||||
countUsers: async () => countUsers(),
|
||||
getDailyNewUserCountsByTimeInterval: async (
|
||||
startTimeExclusive: number,
|
||||
endTimeInclusive: number
|
||||
) => getDailyNewUserCountsByTimeInterval(startTimeExclusive, endTimeInclusive),
|
||||
}));
|
||||
|
||||
const mockDailyNewUserCounts = [
|
||||
|
@ -32,9 +39,6 @@ const mockDailyActiveUserCounts = [
|
|||
|
||||
const mockActiveUserCount = 1000;
|
||||
|
||||
const getDailyNewUserCountsByTimeInterval = jest.fn(
|
||||
async (startTimeExclusive: number, endTimeInclusive: number) => mockDailyNewUserCounts
|
||||
);
|
||||
const getDailyActiveUserCountsByTimeInterval = jest.fn(
|
||||
async (startTimeExclusive: number, endTimeInclusive: number) => mockDailyActiveUserCounts
|
||||
);
|
||||
|
@ -43,10 +47,6 @@ const countActiveUsersByTimeInterval = jest.fn(
|
|||
);
|
||||
|
||||
jest.mock('@/queries/log', () => ({
|
||||
getDailyNewUserCountsByTimeInterval: async (
|
||||
startTimeExclusive: number,
|
||||
endTimeInclusive: number
|
||||
) => getDailyNewUserCountsByTimeInterval(startTimeExclusive, endTimeInclusive),
|
||||
getDailyActiveUserCountsByTimeInterval: async (
|
||||
startTimeExclusive: number,
|
||||
endTimeInclusive: number
|
||||
|
|
|
@ -6,9 +6,8 @@ import koaGuard from '@/middleware/koa-guard';
|
|||
import {
|
||||
countActiveUsersByTimeInterval,
|
||||
getDailyActiveUserCountsByTimeInterval,
|
||||
getDailyNewUserCountsByTimeInterval,
|
||||
} from '@/queries/log';
|
||||
import { countUsers } from '@/queries/user';
|
||||
import { countUsers, getDailyNewUserCountsByTimeInterval } from '@/queries/user';
|
||||
|
||||
import { AuthedRouter } from './types';
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import { sql } from 'slonik';
|
||||
|
||||
import { AlterationScript } from '../lib/types/alteration';
|
||||
|
||||
const alteration: AlterationScript = {
|
||||
up: async (pool) => {
|
||||
await pool.query(sql`
|
||||
alter table users add column created_at timestamptz not null default (now());
|
||||
create index users__created_at on users (created_at);
|
||||
`);
|
||||
},
|
||||
down: async (pool) => {
|
||||
await pool.query(sql`
|
||||
drop index users__created_at;
|
||||
alter table users drop column created_at;
|
||||
`);
|
||||
},
|
||||
};
|
||||
|
||||
export default alteration;
|
|
@ -11,6 +11,7 @@ export const userInfoSelectFields = Object.freeze([
|
|||
'customData',
|
||||
'identities',
|
||||
'lastSignInAt',
|
||||
'createdAt',
|
||||
'applicationId',
|
||||
] as const);
|
||||
|
||||
|
|
|
@ -14,5 +14,8 @@ create table users (
|
|||
identities jsonb /* @use Identities */ not null default '{}'::jsonb,
|
||||
custom_data jsonb /* @use ArbitraryObject */ not null default '{}'::jsonb,
|
||||
last_sign_in_at timestamptz,
|
||||
created_at timestamptz not null default (now()),
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create index users__created_at on users (created_at);
|
||||
|
|
Loading…
Reference in a new issue