0
Fork 0
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:
IceHe 2022-09-29 15:32:43 +08:00 committed by GitHub
parent 11381afdc5
commit cce2d40160
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 51 additions and 30 deletions

View file

@ -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,
}, },
]; ];

View file

@ -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'];

View file

@ -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})
`);

View file

@ -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

View file

@ -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';

View file

@ -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;

View file

@ -11,6 +11,7 @@ export const userInfoSelectFields = Object.freeze([
'customData', 'customData',
'identities', 'identities',
'lastSignInAt', 'lastSignInAt',
'createdAt',
'applicationId', 'applicationId',
] as const); ] as const);

View file

@ -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);