From cce2d401605f02d4d2d15b3f76dd58020c003e38 Mon Sep 17 00:00:00 2001 From: IceHe Date: Thu, 29 Sep 2022 15:32:43 +0800 Subject: [PATCH] refactor(core): count new users by created_at (#2027) --- packages/core/src/__mocks__/user.ts | 7 +++++++ packages/core/src/queries/log.ts | 21 ------------------- packages/core/src/queries/user.ts | 12 +++++++++++ packages/core/src/routes/dashboard.test.ts | 14 ++++++------- packages/core/src/routes/dashboard.ts | 3 +-- ...64356000-add-created-at-column-to-users.ts | 20 ++++++++++++++++++ packages/schemas/src/types/user.ts | 1 + packages/schemas/tables/users.sql | 3 +++ 8 files changed, 51 insertions(+), 30 deletions(-) create mode 100644 packages/schemas/alterations/next-1664356000-add-created-at-column-to-users.ts diff --git a/packages/core/src/__mocks__/user.ts b/packages/core/src/__mocks__/user.ts index ab9740737..bf687d86e 100644 --- a/packages/core/src/__mocks__/user.ts +++ b/packages/core/src/__mocks__/user.ts @@ -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, }, ]; diff --git a/packages/core/src/queries/log.ts b/packages/core/src/queries/log.ts index ee3445594..5b1628c22 100644 --- a/packages/core/src/queries/log.ts +++ b/packages/core/src/queries/log.ts @@ -49,27 +49,6 @@ export const findLogs = async (limit: number, offset: number, logCondition: LogC export const findLogById = buildFindEntityById(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']; diff --git a/packages/core/src/queries/user.ts b/packages/core/src/queries/user.ts index aa84a5911..ea239f289 100644 --- a/packages/core/src/queries/user.ts +++ b/packages/core/src/queries/user.ts @@ -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}) + `); diff --git a/packages/core/src/routes/dashboard.test.ts b/packages/core/src/routes/dashboard.test.ts index e2456a36e..89d75af4d 100644 --- a/packages/core/src/routes/dashboard.test.ts +++ b/packages/core/src/routes/dashboard.test.ts @@ -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 diff --git a/packages/core/src/routes/dashboard.ts b/packages/core/src/routes/dashboard.ts index f5ca33687..6f5417a4f 100644 --- a/packages/core/src/routes/dashboard.ts +++ b/packages/core/src/routes/dashboard.ts @@ -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'; diff --git a/packages/schemas/alterations/next-1664356000-add-created-at-column-to-users.ts b/packages/schemas/alterations/next-1664356000-add-created-at-column-to-users.ts new file mode 100644 index 000000000..5203c9a21 --- /dev/null +++ b/packages/schemas/alterations/next-1664356000-add-created-at-column-to-users.ts @@ -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; diff --git a/packages/schemas/src/types/user.ts b/packages/schemas/src/types/user.ts index 2b06f56b4..d7791450a 100644 --- a/packages/schemas/src/types/user.ts +++ b/packages/schemas/src/types/user.ts @@ -11,6 +11,7 @@ export const userInfoSelectFields = Object.freeze([ 'customData', 'identities', 'lastSignInAt', + 'createdAt', 'applicationId', ] as const); diff --git a/packages/schemas/tables/users.sql b/packages/schemas/tables/users.sql index fdd609b3b..0b0510add 100644 --- a/packages/schemas/tables/users.sql +++ b/packages/schemas/tables/users.sql @@ -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);