mirror of
https://github.com/logto-io/logto.git
synced 2025-01-06 20:40:08 -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: {},
|
customData: {},
|
||||||
applicationId: 'bar',
|
applicationId: 'bar',
|
||||||
lastSignInAt: 1_650_969_465_789,
|
lastSignInAt: 1_650_969_465_789,
|
||||||
|
createdAt: 1_650_969_000_000,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mockUserResponse = pick(mockUser, ...userInfoSelectFields);
|
export const mockUserResponse = pick(mockUser, ...userInfoSelectFields);
|
||||||
|
@ -38,6 +39,7 @@ export const mockUserWithPassword: User = {
|
||||||
customData: {},
|
customData: {},
|
||||||
applicationId: 'bar',
|
applicationId: 'bar',
|
||||||
lastSignInAt: 1_650_969_465_789,
|
lastSignInAt: 1_650_969_465_789,
|
||||||
|
createdAt: 1_650_969_000_000,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mockUserList: User[] = [
|
export const mockUserList: User[] = [
|
||||||
|
@ -55,6 +57,7 @@ export const mockUserList: User[] = [
|
||||||
customData: {},
|
customData: {},
|
||||||
applicationId: 'bar',
|
applicationId: 'bar',
|
||||||
lastSignInAt: 1_650_969_465_000,
|
lastSignInAt: 1_650_969_465_000,
|
||||||
|
createdAt: 1_650_969_000_000,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
|
@ -70,6 +73,7 @@ export const mockUserList: User[] = [
|
||||||
customData: {},
|
customData: {},
|
||||||
applicationId: 'bar',
|
applicationId: 'bar',
|
||||||
lastSignInAt: 1_650_969_465_000,
|
lastSignInAt: 1_650_969_465_000,
|
||||||
|
createdAt: 1_650_969_000_000,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '3',
|
id: '3',
|
||||||
|
@ -85,6 +89,7 @@ export const mockUserList: User[] = [
|
||||||
customData: {},
|
customData: {},
|
||||||
applicationId: 'bar',
|
applicationId: 'bar',
|
||||||
lastSignInAt: 1_650_969_465_000,
|
lastSignInAt: 1_650_969_465_000,
|
||||||
|
createdAt: 1_650_969_000_000,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '4',
|
id: '4',
|
||||||
|
@ -100,6 +105,7 @@ export const mockUserList: User[] = [
|
||||||
customData: {},
|
customData: {},
|
||||||
applicationId: 'bar',
|
applicationId: 'bar',
|
||||||
lastSignInAt: 1_650_969_465_000,
|
lastSignInAt: 1_650_969_465_000,
|
||||||
|
createdAt: 1_650_969_000_000,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '5',
|
id: '5',
|
||||||
|
@ -115,6 +121,7 @@ export const mockUserList: User[] = [
|
||||||
customData: {},
|
customData: {},
|
||||||
applicationId: 'bar',
|
applicationId: 'bar',
|
||||||
lastSignInAt: 1_650_969_465_000,
|
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);
|
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)
|
// 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.
|
// or exchange the access token, which will expire in 2 hours, by the refresh token.
|
||||||
const activeUserLogTypes: LogType[] = ['CodeExchangeToken', 'RefreshTokenExchangeToken'];
|
const activeUserLogTypes: LogType[] = ['CodeExchangeToken', 'RefreshTokenExchangeToken'];
|
||||||
|
|
|
@ -158,3 +158,15 @@ export const hasActiveUsers = async () =>
|
||||||
from ${table}
|
from ${table}
|
||||||
limit 1
|
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 totalUserCount = 1000;
|
||||||
const countUsers = jest.fn(async () => ({ count: totalUserCount }));
|
const countUsers = jest.fn(async () => ({ count: totalUserCount }));
|
||||||
|
const getDailyNewUserCountsByTimeInterval = jest.fn(
|
||||||
|
async (startTimeExclusive: number, endTimeInclusive: number) => mockDailyNewUserCounts
|
||||||
|
);
|
||||||
|
|
||||||
jest.mock('@/queries/user', () => ({
|
jest.mock('@/queries/user', () => ({
|
||||||
countUsers: async () => countUsers(),
|
countUsers: async () => countUsers(),
|
||||||
|
getDailyNewUserCountsByTimeInterval: async (
|
||||||
|
startTimeExclusive: number,
|
||||||
|
endTimeInclusive: number
|
||||||
|
) => getDailyNewUserCountsByTimeInterval(startTimeExclusive, endTimeInclusive),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const mockDailyNewUserCounts = [
|
const mockDailyNewUserCounts = [
|
||||||
|
@ -32,9 +39,6 @@ const mockDailyActiveUserCounts = [
|
||||||
|
|
||||||
const mockActiveUserCount = 1000;
|
const mockActiveUserCount = 1000;
|
||||||
|
|
||||||
const getDailyNewUserCountsByTimeInterval = jest.fn(
|
|
||||||
async (startTimeExclusive: number, endTimeInclusive: number) => mockDailyNewUserCounts
|
|
||||||
);
|
|
||||||
const getDailyActiveUserCountsByTimeInterval = jest.fn(
|
const getDailyActiveUserCountsByTimeInterval = jest.fn(
|
||||||
async (startTimeExclusive: number, endTimeInclusive: number) => mockDailyActiveUserCounts
|
async (startTimeExclusive: number, endTimeInclusive: number) => mockDailyActiveUserCounts
|
||||||
);
|
);
|
||||||
|
@ -43,10 +47,6 @@ const countActiveUsersByTimeInterval = jest.fn(
|
||||||
);
|
);
|
||||||
|
|
||||||
jest.mock('@/queries/log', () => ({
|
jest.mock('@/queries/log', () => ({
|
||||||
getDailyNewUserCountsByTimeInterval: async (
|
|
||||||
startTimeExclusive: number,
|
|
||||||
endTimeInclusive: number
|
|
||||||
) => getDailyNewUserCountsByTimeInterval(startTimeExclusive, endTimeInclusive),
|
|
||||||
getDailyActiveUserCountsByTimeInterval: async (
|
getDailyActiveUserCountsByTimeInterval: async (
|
||||||
startTimeExclusive: number,
|
startTimeExclusive: number,
|
||||||
endTimeInclusive: number
|
endTimeInclusive: number
|
||||||
|
|
|
@ -6,9 +6,8 @@ import koaGuard from '@/middleware/koa-guard';
|
||||||
import {
|
import {
|
||||||
countActiveUsersByTimeInterval,
|
countActiveUsersByTimeInterval,
|
||||||
getDailyActiveUserCountsByTimeInterval,
|
getDailyActiveUserCountsByTimeInterval,
|
||||||
getDailyNewUserCountsByTimeInterval,
|
|
||||||
} from '@/queries/log';
|
} from '@/queries/log';
|
||||||
import { countUsers } from '@/queries/user';
|
import { countUsers, getDailyNewUserCountsByTimeInterval } from '@/queries/user';
|
||||||
|
|
||||||
import { AuthedRouter } from './types';
|
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',
|
'customData',
|
||||||
'identities',
|
'identities',
|
||||||
'lastSignInAt',
|
'lastSignInAt',
|
||||||
|
'createdAt',
|
||||||
'applicationId',
|
'applicationId',
|
||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
|
|
|
@ -14,5 +14,8 @@ create table users (
|
||||||
identities jsonb /* @use Identities */ not null default '{}'::jsonb,
|
identities jsonb /* @use Identities */ not null default '{}'::jsonb,
|
||||||
custom_data jsonb /* @use ArbitraryObject */ not null default '{}'::jsonb,
|
custom_data jsonb /* @use ArbitraryObject */ not null default '{}'::jsonb,
|
||||||
last_sign_in_at timestamptz,
|
last_sign_in_at timestamptz,
|
||||||
|
created_at timestamptz not null default (now()),
|
||||||
primary key (id)
|
primary key (id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create index users__created_at on users (created_at);
|
||||||
|
|
Loading…
Reference in a new issue